command scope interface
This commit is contained in:
parent
cc505a4025
commit
fb6fd4762b
@ -1,18 +1,36 @@
|
|||||||
.root {
|
.root {
|
||||||
width: 350px;
|
width: 350px;
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.input {
|
|
||||||
background: none transparent;
|
|
||||||
border: 0 none;
|
|
||||||
font-size: inherit;
|
|
||||||
outline: none;
|
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.inputArea {
|
.inputArea {
|
||||||
border: solid 1px #000;
|
display: table;
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 3px;
|
||||||
|
box-shadow: inset 0 1px 2px rgba(0,0,0,0.075);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputScope {
|
||||||
|
display: table-cell;
|
||||||
|
width: 1%;
|
||||||
|
padding-right: 6px;
|
||||||
|
padding-left: 8px;
|
||||||
|
color: #767676;
|
||||||
|
white-space: nowrap;
|
||||||
|
vertical-align: middle;
|
||||||
|
border-right: 1px solid #eee;
|
||||||
|
border-top-left-radius: 2px;
|
||||||
|
border-bottom-left-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputField {
|
||||||
|
display: table-cell;
|
||||||
|
width: 99%;
|
||||||
|
background: none transparent;
|
||||||
|
border: 0 none;
|
||||||
|
box-shadow: none;
|
||||||
|
outline: none;
|
||||||
|
font-size: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu {
|
.menu {
|
||||||
@ -20,9 +38,8 @@
|
|||||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||||
background: rgba(255, 255, 255, 0.9);
|
background: rgba(255, 255, 255, 0.9);
|
||||||
padding: 2px 0;
|
padding: 2px 0;
|
||||||
overflow: auto;
|
|
||||||
width: 350px;
|
width: 350px;
|
||||||
height: 100px;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.command {
|
.command {
|
||||||
|
@ -9,11 +9,15 @@ class FindBar extends Component {
|
|||||||
super(props);
|
super(props);
|
||||||
this._compiledCommands = {};
|
this._compiledCommands = {};
|
||||||
this.state = {
|
this.state = {
|
||||||
value: this.props.initialValue,
|
value: '',
|
||||||
|
placeholder: '',
|
||||||
|
activeScope: null,
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
highlightedIndex: 0,
|
highlightedIndex: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this._getSuggestions = _.memoize(this._getSuggestions, (value, activeScope) => value + activeScope);
|
||||||
|
|
||||||
this.compileCommand = this.compileCommand.bind(this);
|
this.compileCommand = this.compileCommand.bind(this);
|
||||||
this.matchCommand = this.matchCommand.bind(this);
|
this.matchCommand = this.matchCommand.bind(this);
|
||||||
this.handleChange = this.handleChange.bind(this);
|
this.handleChange = this.handleChange.bind(this);
|
||||||
@ -21,7 +25,7 @@ class FindBar extends Component {
|
|||||||
this.handleInputBlur = this.handleInputBlur.bind(this);
|
this.handleInputBlur = this.handleInputBlur.bind(this);
|
||||||
this.handleInputFocus = this.handleInputFocus.bind(this);
|
this.handleInputFocus = this.handleInputFocus.bind(this);
|
||||||
this.handleInputClick = this.handleInputClick.bind(this);
|
this.handleInputClick = this.handleInputClick.bind(this);
|
||||||
this.getSuggestions = _.memoize(this.getSuggestions);
|
this.getSuggestions = this.getSuggestions.bind(this);
|
||||||
this.highlightCommandFromMouse = this.highlightCommandFromMouse.bind(this);
|
this.highlightCommandFromMouse = this.highlightCommandFromMouse.bind(this);
|
||||||
this.selectCommandFromMouse = this.selectCommandFromMouse.bind(this);
|
this.selectCommandFromMouse = this.selectCommandFromMouse.bind(this);
|
||||||
this.setIgnoreBlur = this.setIgnoreBlur.bind(this);
|
this.setIgnoreBlur = this.setIgnoreBlur.bind(this);
|
||||||
@ -66,36 +70,57 @@ class FindBar extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
matchCommand(string) {
|
matchCommand() {
|
||||||
|
const string = this.state.activeScope ? this.state.activeScope + this.state.value : this.state.value;
|
||||||
let match;
|
let match;
|
||||||
const command = this.compiledCommands.find(command => {
|
const command = this._compiledCommands.find(command => {
|
||||||
match = string.match(RegExp(`^${command.regexp}`, 'i'));
|
match = string.match(RegExp(`^${command.regexp}`, 'i'));
|
||||||
return match;
|
return match;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (command === null) return null;
|
const param = match[1] && match[1].trim();
|
||||||
|
|
||||||
|
if (!command) {
|
||||||
|
return null;
|
||||||
|
} else if (command && !param) {
|
||||||
|
this.setState({
|
||||||
|
value: '',
|
||||||
|
activeScope: command.token,
|
||||||
|
placeholder: command.param.display
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log({
|
||||||
|
command,
|
||||||
|
param
|
||||||
|
})
|
||||||
return {
|
return {
|
||||||
command,
|
command,
|
||||||
param: match[1] && match[1].trim()
|
param
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChange(event) {
|
handleChange(event) {
|
||||||
this.setState({
|
this.setState({
|
||||||
value: event.target.value,
|
value: event.target.value,
|
||||||
}, () => {
|
|
||||||
this.props.onChange(event, this.state.value);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyDown(event) {
|
handleKeyDown(event) {
|
||||||
|
let highlightedIndex, index;
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
|
case 'Backspace':
|
||||||
|
if (this.state.value.length === 0 && this.state.activeScope) {
|
||||||
|
this.setState({
|
||||||
|
activeScope: null,
|
||||||
|
placeholder: ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'ArrowDown':
|
case 'ArrowDown':
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
let { highlightedIndex } = this.state;
|
highlightedIndex = this.state.highlightedIndex;
|
||||||
let index = (
|
index = (
|
||||||
highlightedIndex === this.getSuggestions(this.state.value, this._compiledCommands).length - 1 ||
|
highlightedIndex === this.getSuggestions().length - 1 ||
|
||||||
this.state.isOpen === false
|
this.state.isOpen === false
|
||||||
) ? 0 : highlightedIndex + 1;
|
) ? 0 : highlightedIndex + 1;
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -105,10 +130,10 @@ class FindBar extends Component {
|
|||||||
break;
|
break;
|
||||||
case 'ArrowUp':
|
case 'ArrowUp':
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
let { highlightedIndex } = this.state;
|
highlightedIndex = this.state.highlightedIndex;
|
||||||
let index = (
|
index = (
|
||||||
highlightedIndex === 0
|
highlightedIndex === 0
|
||||||
) ? this.getSuggestions(this.state.value, this._compiledCommands).length - 1 : highlightedIndex - 1;
|
) ? this.getSuggestions().length - 1 : highlightedIndex - 1;
|
||||||
this.setState({
|
this.setState({
|
||||||
highlightedIndex: index,
|
highlightedIndex: index,
|
||||||
isOpen: true,
|
isOpen: true,
|
||||||
@ -116,18 +141,21 @@ class FindBar extends Component {
|
|||||||
break;
|
break;
|
||||||
case 'Enter':
|
case 'Enter':
|
||||||
if (this.state.isOpen) {
|
if (this.state.isOpen) {
|
||||||
const command = this.getSuggestions(this.state.value, this._compiledCommands)[this.state.highlightedIndex];
|
const command = this.getSuggestions()[this.state.highlightedIndex];
|
||||||
this.setState({
|
const newState = {
|
||||||
value: command.token,
|
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
highlightedIndex: 0
|
highlightedIndex: 0
|
||||||
}, () => {
|
};
|
||||||
this.refs.input.focus();
|
if (command) {
|
||||||
this.refs.input.setSelectionRange(
|
newState.value = command.token;
|
||||||
|
}
|
||||||
|
this.setState(newState, () => {
|
||||||
|
this._input.focus();
|
||||||
|
this._input.setSelectionRange(
|
||||||
this.state.value.length,
|
this.state.value.length,
|
||||||
this.state.value.length
|
this.state.value.length
|
||||||
);
|
);
|
||||||
this.props.onSelect(this.state.value, command);
|
this.matchCommand();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -163,15 +191,19 @@ class FindBar extends Component {
|
|||||||
this.setState({ isOpen: true });
|
this.setState({ isOpen: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
getSuggestions(value, commands) {
|
_getSuggestions(value, scope, commands) {
|
||||||
|
if (scope) return []; // TODO: Prepare for multiple params & search suggestions
|
||||||
const results = fuzzy.filter(value, commands, {
|
const results = fuzzy.filter(value, commands, {
|
||||||
//pre: '<strong>',
|
//pre: '<strong>',
|
||||||
//post: '</strong>',
|
//post: '</strong>',
|
||||||
extract: el => el.token
|
extract: el => el.token
|
||||||
});
|
});
|
||||||
return results.map(result => result.original);
|
return results.slice(0, 5).map(result => result.original);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSuggestions() {
|
||||||
|
return this._getSuggestions(this.state.value, this.state.activeScope, this._compiledCommands);
|
||||||
|
}
|
||||||
|
|
||||||
highlightCommandFromMouse(index) {
|
highlightCommandFromMouse(index) {
|
||||||
this.setState({ highlightedIndex: index });
|
this.setState({ highlightedIndex: index });
|
||||||
@ -183,8 +215,8 @@ class FindBar extends Component {
|
|||||||
isOpen: false,
|
isOpen: false,
|
||||||
highlightedIndex: 0
|
highlightedIndex: 0
|
||||||
}, () => {
|
}, () => {
|
||||||
this.props.onSelect(this.state.value, command);
|
this.matchCommand();
|
||||||
this.refs.input.focus();
|
this._input.focus();
|
||||||
this.setIgnoreBlur(false);
|
this.setIgnoreBlur(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -194,7 +226,7 @@ class FindBar extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderMenu() {
|
renderMenu() {
|
||||||
const commands = this.getSuggestions(this.state.value, this._compiledCommands).map((command, index) => {
|
const commands = this.getSuggestions().map((command, index) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={this.state.highlightedIndex === index ? styles.highlightedCommand : styles.command}
|
className={this.state.highlightedIndex === index ? styles.highlightedCommand : styles.command}
|
||||||
@ -207,40 +239,40 @@ class FindBar extends Component {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return <div className={styles.menu} children={commands} ref='menu'/>;
|
return commands.length > 0 ? <div className={styles.menu} children={commands} /> : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderActiveScope() {
|
||||||
|
return <div className={styles.inputScope}>{this.state.activeScope}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const menu = this.state.isOpen && this.renderMenu();
|
||||||
|
const scope = this.state.activeScope && this.renderActiveScope();
|
||||||
return (
|
return (
|
||||||
<div className={styles.root}>
|
<div className={styles.root}>
|
||||||
<div className={styles.inputArea}>
|
<label className={styles.inputArea}>
|
||||||
|
{scope}
|
||||||
<input
|
<input
|
||||||
ref="input"
|
className={styles.inputField}
|
||||||
className={styles.input}
|
ref={(c) => this._input = c}
|
||||||
onFocus={this.handleInputFocus}
|
onFocus={this.handleInputFocus}
|
||||||
onBlur={this.handleInputBlur}
|
onBlur={this.handleInputBlur}
|
||||||
onChange={(event) => this.handleChange(event)}
|
onChange={(event) => this.handleChange(event)}
|
||||||
onKeyDown={(event) => this.handleKeyDown(event)}
|
onKeyDown={(event) => this.handleKeyDown(event)}
|
||||||
onClick={this.handleInputClick}
|
onClick={this.handleInputClick}
|
||||||
|
placeholder={this.state.placeholder}
|
||||||
value={this.state.value}
|
value={this.state.value}
|
||||||
/>
|
/>
|
||||||
</div>
|
</label>
|
||||||
{this.state.isOpen && this.renderMenu()}
|
{menu}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FindBar.propTypes = {
|
FindBar.propTypes = {
|
||||||
commands: PropTypes.array,
|
commands: PropTypes.array.isRequired
|
||||||
initialValue: PropTypes.any,
|
|
||||||
onChange: PropTypes.func,
|
|
||||||
onSelect: PropTypes.func,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
FindBar.defaultProps = {
|
|
||||||
initialValue: '',
|
|
||||||
onChange() {},
|
|
||||||
onSelect(value, command) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = FindBar;
|
module.exports = FindBar;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user