From bc364ad1c69d6d294788b0237820fbbcc1a3b6b5 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Thu, 10 Dec 2020 18:53:15 +0100 Subject: [PATCH 1/2] #26 - Fixes for arrow selection --- CHANGELOG.md | 6 ++- src/viewpanel/ViewPanel.tsx | 4 +- src/viewpanel/components/TagPicker.tsx | 65 ++++++++++++++++++-------- src/viewpanel/components/Tags.tsx | 7 +-- 4 files changed, 57 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ae8525c..bd170853 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,12 @@ # Change Log +## [1.11.1] - 2020-12-10 + +- [#26](https://github.com/estruyf/vscode-front-matter/issues/26): Fix for arrow selection in the dropdown. + ## [1.11.0] - 2020-12-10 -- Moved from Material UI Autocomplete to Downshift. This gives more flexibility, and allows to focus the inputs from a VSCode command. +- [#25](https://github.com/estruyf/vscode-front-matter/issues/25): Moved from Material UI Autocomplete to Downshift. This gives more flexibility, and allows to focus the inputs from a VSCode command. - Changed the `Front Matter: Insert ` functionality to open in the panel, instead of using the VSCode dialogs. ## [1.10.0] - 2020-12-03 diff --git a/src/viewpanel/ViewPanel.tsx b/src/viewpanel/ViewPanel.tsx index 716b780c..3ca6222a 100644 --- a/src/viewpanel/ViewPanel.tsx +++ b/src/viewpanel/ViewPanel.tsx @@ -38,7 +38,7 @@ export const ViewPanel: React.FunctionComponent = (props: React (settings && settings.tags && settings.tags.length > 0) && ( ({ key: c.toLowerCase(), value: c }))} freeform={settings.freeform} focussed={focusElm === TagType.tags} unsetFocus={unsetFocus} /> @@ -48,7 +48,7 @@ export const ViewPanel: React.FunctionComponent = (props: React (settings && settings.categories && settings.categories.length > 0) && ( ({ key: c.toLowerCase(), value: c }))} freeform={settings.freeform} focussed={focusElm === TagType.categories} unsetFocus={unsetFocus} /> diff --git a/src/viewpanel/components/TagPicker.tsx b/src/viewpanel/components/TagPicker.tsx index 10a5bbeb..d74d58b3 100644 --- a/src/viewpanel/components/TagPicker.tsx +++ b/src/viewpanel/components/TagPicker.tsx @@ -6,10 +6,12 @@ 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: string[]; + options: KeyValue[]; freeform: boolean; focussed: boolean; unsetFocus: () => void; @@ -21,7 +23,7 @@ export const TagPicker: React.FunctionComponent = (props: React const [ inputValue, setInputValue ] = React.useState(""); const prevSelected = usePrevious(crntSelected); const inputRef = React.useRef(null); - const dsRef = React.useRef | null>(null); + const dsRef = React.useRef | null>(null); /** * Removes an option @@ -66,8 +68,16 @@ export const TagPicker: React.FunctionComponent = (props: React * @param compState */ const onSelect = (selectedItem: string | null) => { + console.log(selectedItem) if (selectedItem) { - const uniqValues = Array.from(new Set([...selected, selectedItem])); + let value = selectedItem || ""; + + const item = options.find(o => o.key === selectedItem.toLowerCase()); + if (item && item.value) { + value = item.value; + } + + const uniqValues = Array.from(new Set([...selected, value])); setSelected(uniqValues); sendUpdate(uniqValues); setInputValue(""); @@ -80,15 +90,15 @@ export const TagPicker: React.FunctionComponent = (props: React */ const onEnterSelection = (event: React.KeyboardEvent, closeCb: () => void) => { if (freeform && event.key === "Enter" && inputValue) { - onSelect(inputValue); - - if (closeCb) { - closeCb(); - } + setTimeout(() => { + onSelect(inputValue); + }, 100); } else if (event.key === "Escape") { if (closeCb) { closeCb(); } + } else { + return true; } } @@ -97,8 +107,20 @@ export const TagPicker: React.FunctionComponent = (props: React * @param option * @param inputValue */ - const filterList = (option: string, inputValue: string | null) => { - return !selected.includes(option) && option.toLowerCase().includes((inputValue || "").toLowerCase()); + 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 + } } React.useEffect(() => { @@ -118,25 +140,30 @@ export const TagPicker: React.FunctionComponent = (props: React

{type}

(item ? item : '')} + onChange={(selected) => onSelect(selected?.key || "")} + itemToString={item => (item ? item.value : '')} inputValue={inputValue} - onInputValueChange={(value) => setInputValue(value)}> + onInputValueChange={(value) => setInputValue(value)} + stateReducer={stateReducer}> { ({ getInputProps, getItemProps, getMenuProps, isOpen, inputValue, getRootProps, openMenu, closeMenu }) => ( <>
- { closeMenu(); unsetFocus(); } } - placeholder={`Pick your ${type.toLowerCase()}`} - onKeyDown={(e) => onEnterSelection(e, closeMenu)} /> + { closeMenu(); unsetFocus(); }, + onKeyDown: (e) => onEnterSelection(e, closeMenu) + })} + placeholder={`Pick your ${type.toLowerCase()}`} />
    { isOpen ? options.filter((option) => filterList(option, inputValue)).map((item, index) => ( -
  • - { item } +
  • + { item.value }
  • )) : null } diff --git a/src/viewpanel/components/Tags.tsx b/src/viewpanel/components/Tags.tsx index 131161d7..8944623b 100644 --- a/src/viewpanel/components/Tags.tsx +++ b/src/viewpanel/components/Tags.tsx @@ -1,9 +1,10 @@ import * as React from 'react'; import { Tag } from './Tag'; +import { KeyValue } from './TagPicker'; export interface ITagsProps { values: string[]; - options: string[]; + options: KeyValue[]; onCreate: (tags: string) => void; onRemove: (tags: string) => void; @@ -12,8 +13,8 @@ export interface ITagsProps { export const Tags: React.FunctionComponent = (props: React.PropsWithChildren) => { const { values, options, onCreate, onRemove } = props; - const knownTags = values.filter(v => options.includes(v)); - const unknownTags = values.filter(v => !options.includes(v)); + const knownTags = values.filter(v => options.map(o => o.value).includes(v)); + const unknownTags = values.filter(v => !options.map(o => o.value).includes(v)); return (
    From daac7883b8c929cd271e1ef62a0f4c569ea0bdd8 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Thu, 10 Dec 2020 21:35:31 +0100 Subject: [PATCH 2/2] #26 - Fix for arrow selection in the dropdown --- assets/media/styles.css | 17 ++++- src/viewpanel/ViewPanel.tsx | 4 +- src/viewpanel/components/TagPicker.tsx | 102 ++++++++++++------------- src/viewpanel/components/Tags.tsx | 7 +- 4 files changed, 68 insertions(+), 62 deletions(-) diff --git a/assets/media/styles.css b/assets/media/styles.css index eef8e641..973a3ea9 100644 --- a/assets/media/styles.css +++ b/assets/media/styles.css @@ -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); diff --git a/src/viewpanel/ViewPanel.tsx b/src/viewpanel/ViewPanel.tsx index 3ca6222a..716b780c 100644 --- a/src/viewpanel/ViewPanel.tsx +++ b/src/viewpanel/ViewPanel.tsx @@ -38,7 +38,7 @@ export const ViewPanel: React.FunctionComponent = (props: React (settings && settings.tags && settings.tags.length > 0) && ( ({ 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 = (props: React (settings && settings.categories && settings.categories.length > 0) && ( ({ key: c.toLowerCase(), value: c }))} + options={settings.categories} freeform={settings.freeform} focussed={focusElm === TagType.categories} unsetFocus={unsetFocus} /> diff --git a/src/viewpanel/components/TagPicker.tsx b/src/viewpanel/components/TagPicker.tsx index d74d58b3..bf6d8551 100644 --- a/src/viewpanel/components/TagPicker.tsx +++ b/src/viewpanel/components/TagPicker.tsx @@ -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 = (props: React const [ inputValue, setInputValue ] = React.useState(""); const prevSelected = usePrevious(crntSelected); const inputRef = React.useRef(null); - const dsRef = React.useRef | null>(null); + const dsRef = React.useRef | null>(null); /** * Removes an option @@ -60,7 +58,7 @@ export const TagPicker: React.FunctionComponent = (props: React if (focussed && inputRef && inputRef.current) { inputRef.current.focus(); } - } + }; /** * On item selection @@ -68,13 +66,12 @@ export const TagPicker: React.FunctionComponent = (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 = (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, 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 = (props: React

    {type}

    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 }) => ( <> -
    - { closeMenu(); unsetFocus(); }, - onKeyDown: (e) => onEnterSelection(e, closeMenu) - })} +
    + { + closeMenu(); + unsetFocus(); + if (!inputValue) { + clearSelection(); + } + } + }) + } placeholder={`Pick your ${type.toLowerCase()}`} /> + + { + freeform && ( + + ) + }
      { isOpen ? options.filter((option) => filterList(option, inputValue)).map((item, index) => ( -
    • - { item.value } +
    • + { item }
    • )) : null } @@ -173,7 +165,7 @@ export const TagPicker: React.FunctionComponent = (props: React } - + a.toLowerCase() < b.toLowerCase() ? -1 : 1 )} onRemove={onRemove} onCreate={onCreate} options={options} />
    ); }; \ No newline at end of file diff --git a/src/viewpanel/components/Tags.tsx b/src/viewpanel/components/Tags.tsx index 8944623b..131161d7 100644 --- a/src/viewpanel/components/Tags.tsx +++ b/src/viewpanel/components/Tags.tsx @@ -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 = (props: React.PropsWithChildren) => { 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 (