Autocomplete dropdown for relations editor

This commit is contained in:
Rainer Simon 2020-07-09 11:20:33 +02:00
parent d47d93566e
commit 24458e26f0
5 changed files with 129 additions and 24 deletions

28
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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 (
<div ref={element}>
<div {...getComboboxProps()}>
<input
{...getInputProps({ onKeyDown: evt => { if (!isOpen) props.onKeyDown(evt); } })}
placeholder={props.placeholder}
value={props.content} />
</div>
<ul {...getMenuProps()}>
{isOpen && inputItems.map((item, index) => (
<li style={
highlightedIndex === index
? { backgroundColor: '#bde4ff' }
: {}
}
key={`${item}${index}`}
{...getItemProps({ item, index })}>
{item}
</li>
))}
</ul>
</div>
)
}
export default RelationAutocomplete;

View File

@ -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(
<div className="r6o-relation-editor" ref={this.element}>
<div className="input-wrapper">
<ContentEditable
className="input"
html={this.state.content}
data-placeholder="Tag..."
<RelationAutocomplete
content={this.state.content}
placeholder="Tag..."
onChange={this.onChange}
onKeyDown={this.onKeyDown}
/>
onKeyDown={this.onKeyDown}
vocabulary={this.props.vocabulary || []} />
</div>
<div className="buttons">

View File

@ -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 {