From 24458e26f066985061581ffd5d3fc712eb2cb64b Mon Sep 17 00:00:00 2001 From: Rainer Simon Date: Thu, 9 Jul 2020 11:20:33 +0200 Subject: [PATCH] Autocomplete dropdown for relations editor --- package-lock.json | 28 +++++++++- package.json | 1 + src/relations/editor/RelationAutocomplete.js | 55 ++++++++++++++++++++ src/relations/editor/RelationEditor.jsx | 19 +++---- themes/default/relations/_editor.scss | 50 +++++++++++++----- 5 files changed, 129 insertions(+), 24 deletions(-) create mode 100644 src/relations/editor/RelationAutocomplete.js diff --git a/package-lock.json b/package-lock.json index 7b7069b..8122c39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@recogito/recogito-client-core", - "version": "0.1.5", + "version": "0.1.6", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2315,6 +2315,11 @@ } } }, + "compute-scroll-into-view": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.14.tgz", + "integrity": "sha512-mKDjINe3tc6hGelUMNDzuhorIUZ7kS7BwyY0r2wQd2HOH2tRuJykiC06iSEX8y1TuhNzvz4GcJnK16mM2J1NMQ==" + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2804,6 +2809,27 @@ "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", "dev": true }, + "downshift": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/downshift/-/downshift-5.4.6.tgz", + "integrity": "sha512-GtSCmZUQMulQQ0gX3N3jvUAABJNU8IfAMLzFLu0E2fcOTt98xropy0iriYW2PSClRUqJ4QwKAov7FDy9Gk9aOA==", + "requires": { + "@babel/runtime": "^7.10.2", + "compute-scroll-into-view": "^1.0.14", + "prop-types": "^15.7.2", + "react-is": "^16.13.1" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.4.tgz", + "integrity": "sha512-UpTN5yUJr9b4EX2CnGNWIvER7Ab83ibv0pcvvHc4UOdrBI5jb8bj+32cCwPX6xu0mt2daFNjYhoi+X7beH0RSw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + } + } + }, "duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", diff --git a/package.json b/package.json index b01d51a..401cb6e 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ }, "dependencies": { "axios": "^0.19.0", + "downshift": "^5.4.6", "node-polyglot": "^2.4.0", "preact": "^10.4.1", "react-contenteditable": "^3.3.3", diff --git a/src/relations/editor/RelationAutocomplete.js b/src/relations/editor/RelationAutocomplete.js new file mode 100644 index 0000000..5165037 --- /dev/null +++ b/src/relations/editor/RelationAutocomplete.js @@ -0,0 +1,55 @@ +import React, { useState, useEffect, useRef } from 'react' +import { useCombobox } from 'downshift' + +const RelationAutocomplete = props => { + + const element = useRef(); + + const [ inputItems, setInputItems ] = useState(props.vocabulary); + + useEffect(() => + element.current?.querySelector('input').focus(), []); + + const onInputValueChange = ({ inputValue }) => { + props.onChange(inputValue); + setInputItems( + props.vocabulary.filter(item => + item.toLowerCase().startsWith(inputValue.toLowerCase()))) + } + + const { + isOpen, + getMenuProps, + getInputProps, + getComboboxProps, + highlightedIndex, + getItemProps, + } = useCombobox({ items: inputItems, onInputValueChange }); + + return ( +
+
+ { if (!isOpen) props.onKeyDown(evt); } })} + placeholder={props.placeholder} + value={props.content} /> +
+ +
+ ) + +} + +export default RelationAutocomplete; \ No newline at end of file diff --git a/src/relations/editor/RelationEditor.jsx b/src/relations/editor/RelationEditor.jsx index b978162..e435366 100644 --- a/src/relations/editor/RelationEditor.jsx +++ b/src/relations/editor/RelationEditor.jsx @@ -1,6 +1,6 @@ import React, { Component } from 'preact/compat'; -import ContentEditable from 'react-contenteditable'; import { TrashIcon, CheckIcon } from '../../Icons'; +import RelationAutocomplete from './RelationAutocomplete'; /** * Shorthand to get the label (= first tag body value) from the @@ -54,13 +54,11 @@ export default class RelationEditor extends Component { el.style.top = `${midY}px`; el.style.left = `${midX}px`; - - setTimeout(() => el.querySelector('.input').focus(), 0); } } - onChange = evt => - this.setState({ content: evt.target.value }); + onChange = value => + this.setState({ content: value }); onKeyDown = evt => { if (evt.which === 13) { // Enter = Submit @@ -103,13 +101,12 @@ export default class RelationEditor extends Component { return(
- + onKeyDown={this.onKeyDown} + vocabulary={this.props.vocabulary || []} />
diff --git a/themes/default/relations/_editor.scss b/themes/default/relations/_editor.scss index 983d455..68114ce 100644 --- a/themes/default/relations/_editor.scss +++ b/themes/default/relations/_editor.scss @@ -14,30 +14,56 @@ } * { - line-height:31px; box-sizing:border-box; } .input-wrapper { height:34px; - padding:10px 6px; + padding:0 6px; margin-right:68px; font-size:14px; - min-width:80px; background-color:$blueish-white; cursor:text; @include rounded-corners-left(3px); - } - .input { - outline:none; - line-height:14px; - white-space:pre; - } + div[role=combobox] { + height:34px; + } - .input:empty:before { - content:attr(data-placeholder); - color:darken($lightgrey-border-darker, 15%); + input { + outline:none; + border:none; + width:80px; + height:100%; + line-height:14px; + white-space:pre; + box-sizing:border-box; + background-color:transparent; + font-size:14px; + color:$standard-type; + } + + ul { + position:relative; + left:-6px; + margin:0; + padding:0; + list-style-type:none; + background-color:#fff; + min-width:100%; + border-radius:3px; + border:1px solid #d6d7d9; + box-sizing:border-box; + box-shadow:0 0 20px rgba(0,0,0,0.25); + } + + li { + box-sizing:border-box; + padding:2px 12px; + width:100%; + cursor:pointer; + } + } .buttons {