diff --git a/Picker.js b/Picker.js
index b7cc15b..77453b9 100644
--- a/Picker.js
+++ b/Picker.js
@@ -4,7 +4,7 @@
// ...Or maybe a button with a popup menu
// License: LGPLv3.0+
// (c) Vitaliy Filippov 2019+
-// Version 2019-09-03
+// Version 2020-04-27
import React from 'react';
import ReactDOM from 'react-dom';
@@ -78,15 +78,25 @@ export default class Picker extends React.Component
this.picker = e;
}
+ getInputProps()
+ {
+ return {
+ onFocus: this.focus,
+ onBlur: this.blur,
+ focused: this.state.focused,
+ ref: this.setInput,
+ };
+ }
+
+ renderPicker()
+ {
+ return this.props.renderPicker();
+ }
+
render()
{
return (
- {this.props.renderInput({
- onFocus: this.focus,
- onBlur: this.blur,
- focused: this.state.focused,
- ref: this.setInput,
- })}
+ {this.props.renderInput(this.getInputProps())}
{this.state.focused
?
- {this.props.renderPicker()}
+ {this.renderPicker()}
: null}
);
diff --git a/PickerMenu.js b/PickerMenu.js
new file mode 100644
index 0000000..28013bc
--- /dev/null
+++ b/PickerMenu.js
@@ -0,0 +1,136 @@
+// Menu-like Picker variant with keyboard control
+// Version 2020-04-27
+// License: LGPLv3.0+
+// (c) Vitaliy Filippov 2020+
+
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import autocomplete_css from './autocomplete.css';
+import Picker from './Picker.js';
+
+export default class PickerMenu extends Picker
+{
+ static propTypes = {
+ ...Picker.propTypes,
+ // menu options
+ items: PropTypes.array,
+ // additional text/items to render before menu items
+ beforeItems: PropTypes.any,
+ // additional text/items to render after menu items
+ afterItems: PropTypes.any,
+ // menuitem callback
+ onSelectItem: PropTypes.func,
+ // menuitem name key - default empty (render the item itself)
+ labelKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ // change theme (CSS module) for this input
+ theme: PropTypes.object,
+ }
+
+ onKeyDown = (ev) =>
+ {
+ if ((ev.which == 40 || ev.which == 38) && this.props.items.length)
+ {
+ // up / down
+ this.setState({
+ active: this.state.active == null ? 0 : (
+ (this.state.active + (event.which === 40 ? 1 : this.props.items.length-1)) % this.props.items.length
+ ),
+ });
+ if (!this.state.focused)
+ {
+ this.focus();
+ }
+ }
+ else if ((ev.which == 10 || ev.which == 13) && this.state.active != null &&
+ this.state.active < this.props.items.length)
+ {
+ // enter
+ this.onMouseDown();
+ }
+ }
+
+ onMouseDown = () =>
+ {
+ const sel = this.props.items[this.state.active];
+ const f = this.props.onSelectItem;
+ f && f(sel);
+ }
+
+ onMouseOver = (ev) =>
+ {
+ let e = ev.target;
+ while (e && e != ev.currentTarget && !e.id)
+ {
+ e = e.parentNode;
+ }
+ if (e && e.id)
+ {
+ this.setState({ active: e.id });
+ }
+ }
+
+ animatePicker = (e) =>
+ {
+ if (e)
+ {
+ e.style.visibility = 'hidden';
+ e.style.overflowY = 'hidden';
+ const anim = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame;
+ anim(() =>
+ {
+ e.style.visibility = '';
+ e.style.maxHeight = '1px';
+ anim(() =>
+ {
+ e.style.maxHeight = '100%';
+ const end = () => { e.style.overflowY = 'auto'; e.removeEventListener('transitionend', end); };
+ e.addEventListener('transitionend', end);
+ });
+ });
+ }
+ }
+
+ renderPicker = () =>
+ {
+ const theme = this.props.theme || autocomplete_css;
+ return (
+ {this.props.beforeItems}
+ {this.props.items.map((e, i) => (
+ {!this.props.labelKey ? e : e[this.props.labelKey]}
+
))}
+ {this.props.afterItems}
+
);
+ }
+
+ getInputProps()
+ {
+ return {
+ ...super.getInputProps(),
+ onKeyDown: this.onKeyDown,
+ };
+ }
+
+ componentDidUpdate()
+ {
+ super.componentDidUpdate();
+ if (this.input)
+ {
+ if (this.prevHeight && this.input.offsetHeight != this.prevHeight)
+ {
+ this.calculateDirection();
+ }
+ this.prevHeight = this.input.offsetHeight;
+ }
+ }
+
+ componentDidMount()
+ {
+ this.componentDidUpdate();
+ }
+}
+
+delete PickerMenu.propTypes.renderPicker;
diff --git a/Selectbox.js b/Selectbox.js
index 66bb119..8b34e6e 100644
--- a/Selectbox.js
+++ b/Selectbox.js
@@ -1,5 +1,5 @@
// Simple Dropdown/Autocomplete with single/multiple selection and easy customisation via CSS modules
-// Version 2019-09-15
+// Version 2020-04-27
// License: LGPLv3.0+
// (c) Vitaliy Filippov 2019+
@@ -7,7 +7,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import autocomplete_css from './autocomplete.css';
-import Picker from './Picker.js';
+import PickerMenu from './PickerMenu.js';
export default class Selectbox extends React.PureComponent
{
@@ -54,7 +54,6 @@ export default class Selectbox extends React.PureComponent
state = {
shown: false,
- active: null,
query: null,
inputWidth: 20,
}
@@ -74,29 +73,6 @@ export default class Selectbox extends React.PureComponent
}
}
- onKeyDown = (ev) =>
- {
- if ((ev.which == 40 || ev.which == 38) && this.filtered_items.length)
- {
- // up / down
- this.setState({
- active: this.state.active == null ? 0 : (
- (this.state.active + (event.which === 40 ? 1 : this.filtered_items.length-1)) % this.filtered_items.length
- ),
- });
- if (!this.picker.state.focused)
- {
- this.picker.focus();
- }
- }
- else if ((ev.which == 10 || ev.which == 13) && this.state.active != null &&
- this.state.active < this.filtered_items.length)
- {
- // enter
- this.onMouseDown();
- }
- }
-
clear = () =>
{
this.setState({ query: null });
@@ -116,11 +92,11 @@ export default class Selectbox extends React.PureComponent
}
}
- onMouseDown = () =>
+ onSelectItem = (item) =>
{
this.setState({ query: null });
this.picker.blur();
- const sel = this.filtered_items[this.state.active][this.props.valueKey||'id'];
+ const sel = item[this.props.valueKey||'id'];
let value = sel;
if (this.props.multiple)
{
@@ -141,19 +117,6 @@ export default class Selectbox extends React.PureComponent
f && f(value);
}
- onMouseOver = (ev) =>
- {
- let e = ev.target;
- while (e && e != ev.currentTarget && !e.id)
- {
- e = e.parentNode;
- }
- if (e && e.id)
- {
- this.setState({ active: e.id });
- }
- }
-
focusInput = () =>
{
if (!this.props.disabled)
@@ -168,14 +131,14 @@ export default class Selectbox extends React.PureComponent
{
return;
}
- if (!this.props.multiple && this.state.active === null)
+ if (!this.props.multiple && this.picker.state.active === null)
{
const v = this.props.value, vk = this.props.valueKey||'id';
for (let i = 0; i < this.filtered_items.length; i++)
{
if (v == this.filtered_items[i][vk])
{
- this.setState({ active: i });
+ this.picker.setState({ active: i });
break;
}
}
@@ -213,7 +176,7 @@ export default class Selectbox extends React.PureComponent
onBlur={this.onBlur}
value={value}
onChange={this.setQuery}
- onKeyDown={this.onKeyDown}
+ onKeyDown={p.onKeyDown}
/>;
return ();
}
- animateSuggestions = (e) =>
- {
- if (e)
- {
- e.style.visibility = 'hidden';
- e.style.overflowY = 'hidden';
- const anim = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame;
- anim(() =>
- {
- e.style.visibility = '';
- e.style.maxHeight = '1px';
- anim(() =>
- {
- e.style.maxHeight = '100%';
- const end = () => { e.style.overflowY = 'auto'; e.removeEventListener('transitionend', end); };
- e.addEventListener('transitionend', end);
- });
- });
- }
- }
-
- renderSuggestions = () =>
- {
- const theme = this.props.theme || autocomplete_css;
- return (
- {this.filtered_items.map((e, i) => (
- {e[this.props.labelKey||'name']}
-
))}
- {this.props.suggestionMsg}
-
);
- }
-
setSizer = (e) =>
{
this.sizer = e;
@@ -334,12 +262,15 @@ export default class Selectbox extends React.PureComponent
}
this.prevProps = this.props;
this.prevState = this.state;
- return (
);
}
@@ -348,11 +279,6 @@ export default class Selectbox extends React.PureComponent
if (this.sizer)
{
this.setState({ inputWidth: this.sizer.offsetWidth });
- if (this.prevHeight && this.picker.input.offsetHeight != this.prevHeight)
- {
- this.picker.calculateDirection();
- }
- this.prevHeight = this.picker.input.offsetHeight;
}
}
diff --git a/button.css b/button.css
new file mode 100644
index 0000000..d4565b6
--- /dev/null
+++ b/button.css
@@ -0,0 +1,90 @@
+/* extjs-like