├── .gitignore
├── demo
├── src
│ ├── index.js
│ ├── fruit.js
│ ├── app.styl
│ └── App.js
├── .gitignore
├── README.md
├── package.json
└── public
│ ├── index.html
│ └── bundle.css
├── src
├── styles
│ └── react-datalist.styl
├── components
│ ├── DataListOption.js
│ └── DataList.js
└── ReactDataList.js
├── LICENSE
├── package.json
├── lib
├── components
│ ├── DataListOption.js
│ └── DataList.js
└── ReactDataList.js
├── README.md
└── test
└── spec.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | *.swp
3 |
--------------------------------------------------------------------------------
/demo/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | ReactDOM.render(
6 | ,
7 | document.getElementById('root')
8 | );
9 |
--------------------------------------------------------------------------------
/demo/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | node_modules
5 |
6 | # testing
7 | coverage
8 |
9 | # production
10 | build
11 |
12 | # misc
13 | .DS_Store
14 | .env
15 | npm-debug.log
16 |
--------------------------------------------------------------------------------
/demo/README.md:
--------------------------------------------------------------------------------
1 | React Datalist Demo
2 | ==============================
3 |
4 | This is a runnable demo showing how to use `react-datalist`
5 |
6 | ## Running Example
7 |
8 | **In the demo directory, run:**
9 | ```
10 | $ npm install
11 | $ npm start
12 | ```
13 | **Open [http://localhost:3000](http://localhost:3000) to view it in the browser.**
14 |
--------------------------------------------------------------------------------
/src/styles/react-datalist.styl:
--------------------------------------------------------------------------------
1 | .react-datalist
2 | margin : 0 !important
3 | border : 1px solid #A1C1E7
4 | max-height : 500px
5 | overflow-x : scroll
6 | background-color : white
7 |
8 | .react-datalist-option
9 | display : block
10 | margin : 0 !important
11 | width : 94%
12 | padding : 3%
13 | cursor : pointer
14 |
15 | &:hover
16 | background-color #BAD4FE
17 |
18 | &.react-datalist-option-selected
19 | background-color #BAD4FE
20 |
--------------------------------------------------------------------------------
/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-datalist-demo-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "devDependencies": {
6 | "react-scripts": "0.6.1"
7 | },
8 | "dependencies": {
9 | "react": "^15.3.2",
10 | "react-datalist": "^3.0.0",
11 | "react-dom": "^15.3.2",
12 | "stylusify": "^2.1.0"
13 | },
14 | "scripts": {
15 | "compile-stylus": "stylus src/app.styl -p > public/bundle.css",
16 | "start": "npm run compile-stylus && react-scripts start",
17 | "build": "npm run compile-stylus && react-scripts build"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/DataListOption.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default class DatalistOption extends React.Component {
4 | render() {
5 | var classes = 'react-datalist-option'
6 | if (this.props.selected) classes += ' react-datalist-option-selected'
7 | if (this.props.useNative) return (
8 |
9 | )
10 | return (
11 |
{this.props.option}
12 | )
13 | }
14 | handleClick(e) {
15 | this.props.select(this.props.index)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/demo/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | React-Datalist Demo
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/components/DataList.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import DataListOption from './DataListOption'
3 |
4 | export default class DataList extends React.Component {
5 | render() {
6 | let options = this.props.options.map((option, index) => {
7 | return
14 | })
15 | var containerStyle = {}
16 | if (!this.props.useNative) {
17 | if (this.props.hide) containerStyle.display = 'none'
18 | else if (this.props.options.length == 0) containerStyle.display = 'none'
19 | else containerStyle.display = 'block'
20 | }
21 | if (this.props.useNative) return (
22 | {options}
23 | )
24 | return (
25 | {options}
26 | )
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/demo/src/fruit.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | "Apple",
3 | "Apricot",
4 | "Avocado",
5 | "Banana",
6 | "Bilberry",
7 | "Blackberry",
8 | "Blackcurrant",
9 | "Blueberry",
10 | "Currant",
11 | "Cherry",
12 | "Cherimoya",
13 | "Clementine",
14 | "Date",
15 | "Damson",
16 | "Durian",
17 | "Eggplant",
18 | "Elderberry",
19 | "Feijoa",
20 | "Gooseberry",
21 | "Grape",
22 | "Grapefruit",
23 | "Guava",
24 | "Huckleberry",
25 | "Jackfruit",
26 | "Jambul",
27 | "Kiwi fruit",
28 | "Kumquat",
29 | "Legume",
30 | "Lemon",
31 | "Lime",
32 | "Lychee",
33 | "Mango",
34 | "Mangostine",
35 | "Melon",
36 | "Cantaloupe",
37 | "Honeydew melon",
38 | "Watermelon",
39 | "Rock melon",
40 | "Nectarine",
41 | "Orange",
42 | "Peach",
43 | "Pear",
44 | "Williams pear or Bartlett pear",
45 | "Pitaya",
46 | "Physalis",
47 | "Plum/prune (dried plum)",
48 | "Pineapple",
49 | "Pomegranate",
50 | "Raisin",
51 | "Raspberry",
52 | "Western raspberry (blackcap)",
53 | "Rambutan",
54 | "Redcurrant",
55 | "Salal berry",
56 | "Satsuma",
57 | "Star fruit",
58 | "Strawberry",
59 | "Tangerine",
60 | "Tomato",
61 | "Ugli fruit",
62 | "Watermelon",
63 | "Ziziphus mauritiana"
64 | ]
65 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014, Asbjorn Enge
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 | 2. Redistributions in binary form must reproduce the above copyright notice,
10 | this list of conditions and the following disclaimer in the documentation
11 | and/or other materials provided with the distribution.
12 |
13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 |
24 | The views and conclusions contained in the software and documentation are those
25 | of the authors and should not be interpreted as representing official policies,
26 | either expressed or implied, of the FreeBSD Project.
27 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-datalist",
3 | "version": "4.0.0",
4 | "description": "A polyfill for reactjs",
5 | "main": "./lib/ReactDataList",
6 | "scripts": {
7 | "test": "mocha --compilers js:babel/register,styl:mocha-stylus-compiler -R nyan --check-leaks -w",
8 | "build": "babel --plugins babel-plugin-stylus src/ --out-dir lib"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git://github.com/asbjornenge/react-datalist.git"
13 | },
14 | "keywords": [
15 | "browser",
16 | "datalist",
17 | "dom",
18 | "react",
19 | "react-component"
20 | ],
21 | "author": "Asbjorn Enge ",
22 | "license": "BSD",
23 | "bugs": {
24 | "url": "https://github.com/asbjornenge/react-datalist/issues"
25 | },
26 | "homepage": "https://github.com/asbjornenge/react-datalist",
27 | "browser": {
28 | "jsdom": false
29 | },
30 | "testling": {
31 | "harness": "mocha-bdd",
32 | "files": "test/spec.js",
33 | "browsers": [
34 | "ie/9..latest",
35 | "chrome/23..latest",
36 | "firefox/22..latest",
37 | "safari/latest",
38 | "opera/12..latest",
39 | "iphone/6",
40 | "ipad/6",
41 | "android-browser/latest"
42 | ]
43 | },
44 | "peerDependencies": {
45 | "react": "^15.0.0",
46 | "react-dom": "^15.0.0"
47 | },
48 | "devDependencies": {
49 | "babel": "^5.8.29",
50 | "babel-plugin-stylus": "^1.0.0",
51 | "babelify": "^6.4.0",
52 | "lodash": "^2.4.1",
53 | "mocha": "^1.18.2",
54 | "mocha-stylus-compiler": "^1.0.0",
55 | "nanodom": "0.0.3",
56 | "react-addons-test-utils": "^0.14.1",
57 | "stylusify": "^1.0.0",
58 | "testdom": "^2.0.0"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/demo/src/app.styl:
--------------------------------------------------------------------------------
1 | body
2 | font-family: 'Kaushan Script', 'cursive';
3 |
4 | .react-datalist-container
5 | width 554px
6 | margin auto
7 | margin-top 20px
8 |
9 | .react-datalist-input
10 | width 550px
11 | height 50px
12 | margin auto
13 | padding 0
14 | margin 0
15 | display block
16 | font-size 16px
17 | text-indent 5px
18 |
19 | .react-datalist
20 | font-family none
21 |
22 | .main-label
23 | display block
24 | padding-top 150px
25 | text-align center
26 |
27 | .message-box
28 | width 550px
29 | margin auto
30 | padding-top 30px
31 |
32 | .info-message
33 | text-align center
34 |
35 | span
36 | display block
37 | padding 5px
38 | font-size 18px
39 |
40 | &.choice
41 | font-size 25px
42 | font-weight bold
43 |
44 | img
45 | padding 10px
46 |
47 | /** SWITCH **/
48 |
49 | .onoffswitch {
50 | position: relative; width: 90px;
51 | -webkit-user-select:none; -moz-user-select:none; -ms-user-select: none;
52 | }
53 |
54 | .onoffswitch-checkbox {
55 | display: none;
56 | }
57 |
58 | .onoffswitch-label {
59 | display: block; overflow: hidden; cursor: pointer;
60 | border: 2px solid #999999; border-radius: 20px;
61 | }
62 |
63 | .onoffswitch-inner {
64 | width: 200%; margin-left: -100%;
65 | -moz-transition: margin 0.3s ease-in 0s; -webkit-transition: margin 0.3s ease-in 0s;
66 | -o-transition: margin 0.3s ease-in 0s; transition: margin 0.3s ease-in 0s;
67 | }
68 |
69 | .onoffswitch-inner:before, .onoffswitch-inner:after {
70 | float: left; width: 50%; height: 30px; padding: 0; line-height: 30px;
71 | font-size: 14px; color: white; font-family: Trebuchet, Arial, sans-serif; font-weight: bold;
72 | -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box;
73 | }
74 |
75 | .onoffswitch-inner:before {
76 | content: "Native";
77 | padding-left: 10px;
78 | background-color: #2FCCFF; color: #FFFFFF;
79 | }
80 |
81 | .onoffswitch-inner:after {
82 | content: "Poly";
83 | padding-right: 10px;
84 | background-color: #EEEEEE; color: #999999;
85 | text-align: right;
86 | }
87 |
88 | .onoffswitch-switch {
89 | width: 18px; margin: 6px;
90 | background: #FFFFFF;
91 | border: 2px solid #999999; border-radius: 20px;
92 | position: absolute; top: 0; bottom: 0; right: 56px;
93 | -moz-transition: all 0.3s ease-in 0s; -webkit-transition: all 0.3s ease-in 0s;
94 | -o-transition: all 0.3s ease-in 0s; transition: all 0.3s ease-in 0s;
95 | }
96 |
97 | .onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-inner {
98 | margin-left: 0;
99 | }
100 |
101 | .onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch {
102 | right: 0px;
103 | }
104 |
--------------------------------------------------------------------------------
/demo/public/bundle.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: 'Kaushan Script', 'cursive';
3 | }
4 | .react-datalist-container {
5 | width: 554px;
6 | margin: auto;
7 | margin-top: 20px;
8 | }
9 | .react-datalist-container .react-datalist-input {
10 | width: 550px;
11 | height: 50px;
12 | margin: auto;
13 | padding: 0;
14 | margin: 0;
15 | display: block;
16 | font-size: 16px;
17 | text-indent: 5px;
18 | }
19 | .react-datalist-container .react-datalist {
20 | font-family: none;
21 | }
22 | .main-label {
23 | display: block;
24 | padding-top: 150px;
25 | text-align: center;
26 | }
27 | .message-box {
28 | width: 550px;
29 | margin: auto;
30 | padding-top: 30px;
31 | }
32 | .message-box .info-message {
33 | text-align: center;
34 | }
35 | .message-box .info-message span {
36 | display: block;
37 | padding: 5px;
38 | font-size: 18px;
39 | }
40 | .message-box .info-message span.choice {
41 | font-size: 25px;
42 | font-weight: bold;
43 | }
44 | .message-box .info-message img {
45 | padding: 10px;
46 | }
47 | /** SWITCH **/
48 | .onoffswitch {
49 | position: relative;
50 | width: 90px;
51 | -webkit-user-select: none;
52 | -moz-user-select: none;
53 | -ms-user-select: none;
54 | }
55 | .onoffswitch-checkbox {
56 | display: none;
57 | }
58 | .onoffswitch-label {
59 | display: block;
60 | overflow: hidden;
61 | cursor: pointer;
62 | border: 2px solid #999;
63 | border-radius: 20px;
64 | }
65 | .onoffswitch-inner {
66 | width: 200%;
67 | margin-left: -100%;
68 | -moz-transition: margin 0.3s ease-in 0s;
69 | -webkit-transition: margin 0.3s ease-in 0s;
70 | -o-transition: margin 0.3s ease-in 0s;
71 | transition: margin 0.3s ease-in 0s;
72 | }
73 | .onoffswitch-inner:before,
74 | .onoffswitch-inner:after {
75 | float: left;
76 | width: 50%;
77 | height: 30px;
78 | padding: 0;
79 | line-height: 30px;
80 | font-size: 14px;
81 | color: #fff;
82 | font-family: Trebuchet, Arial, sans-serif;
83 | font-weight: bold;
84 | -moz-box-sizing: border-box;
85 | -webkit-box-sizing: border-box;
86 | box-sizing: border-box;
87 | }
88 | .onoffswitch-inner:before {
89 | content: "Native";
90 | padding-left: 10px;
91 | background-color: #2fccff;
92 | color: #fff;
93 | }
94 | .onoffswitch-inner:after {
95 | content: "Poly";
96 | padding-right: 10px;
97 | background-color: #eee;
98 | color: #999;
99 | text-align: right;
100 | }
101 | .onoffswitch-switch {
102 | width: 18px;
103 | margin: 6px;
104 | background: #fff;
105 | border: 2px solid #999;
106 | border-radius: 20px;
107 | position: absolute;
108 | top: 0;
109 | bottom: 0;
110 | right: 56px;
111 | -moz-transition: all 0.3s ease-in 0s;
112 | -webkit-transition: all 0.3s ease-in 0s;
113 | -o-transition: all 0.3s ease-in 0s;
114 | transition: all 0.3s ease-in 0s;
115 | }
116 | .onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-inner {
117 | margin-left: 0;
118 | }
119 | .onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch {
120 | right: 0px;
121 | }
122 |
--------------------------------------------------------------------------------
/lib/components/DataListOption.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, '__esModule', {
4 | value: true
5 | });
6 |
7 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
8 |
9 | var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
10 |
11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
12 |
13 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
14 |
15 | function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
16 |
17 | var _react = require('react');
18 |
19 | var _react2 = _interopRequireDefault(_react);
20 |
21 | var DatalistOption = (function (_React$Component) {
22 | _inherits(DatalistOption, _React$Component);
23 |
24 | function DatalistOption() {
25 | _classCallCheck(this, DatalistOption);
26 |
27 | _get(Object.getPrototypeOf(DatalistOption.prototype), 'constructor', this).apply(this, arguments);
28 | }
29 |
30 | _createClass(DatalistOption, [{
31 | key: 'render',
32 | value: function render() {
33 | var classes = 'react-datalist-option';
34 | if (this.props.selected) classes += ' react-datalist-option-selected';
35 | if (this.props.useNative) return _react2['default'].createElement('option', { value: this.props.option });
36 | return _react2['default'].createElement(
37 | 'div',
38 | { className: classes, onClick: this.handleClick.bind(this) },
39 | this.props.option
40 | );
41 | }
42 | }, {
43 | key: 'handleClick',
44 | value: function handleClick(e) {
45 | this.props.select(this.props.index);
46 | }
47 | }]);
48 |
49 | return DatalistOption;
50 | })(_react2['default'].Component);
51 |
52 | exports['default'] = DatalistOption;
53 | module.exports = exports['default'];
--------------------------------------------------------------------------------
/demo/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDataList from 'react-datalist'
3 | import fruit from './fruit'
4 |
5 | class NativeSwitch extends React.Component {
6 | constructor(props) {
7 | super(props)
8 | this.state = {
9 | checked : ''
10 | }
11 | }
12 | render() {
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | )
22 | }
23 | toggleNative(event) {
24 | this.props.onChange(this.state.checked === '' ? true : false)
25 | this.setState({ checked : this.state.checked === '' ? 'checked' : '' })
26 | }
27 | }
28 |
29 | class MessageBox extends React.Component {
30 | render() {
31 | var selectedOption, error;
32 | if (this.props.selectedOption !== undefined) {
33 | selectedOption = (
34 |
35 |
Your favorite is...
36 |
{this.props.selectedOption}
37 |
38 |
Good choice!
39 |
40 | )
41 | }
42 | if (this.props.error !== undefined) error = {this.props.error}
43 | return (
44 |
45 | {selectedOption}
46 | {error}
47 |
48 | )
49 | }
50 | }
51 |
52 | class App extends React.Component {
53 | constructor(props) {
54 | super(props)
55 | this.state = {
56 | forcePoly : true,
57 | support : !!('list' in document.createElement('input')) && !!(document.createElement('datalist') && window.HTMLDataListElement)
58 | }
59 | }
60 | render() {
61 | // eslint-disable-next-line
62 | let errorMessage = "";
63 | if (!this.state.support && !this.state.forcePoly) errorMessage = 'Your browser does not support the native datalist :-( No worries, react-datalist got your back.'
64 | return (
65 |
66 |
67 |
Find your favorite fruit!
68 |
73 |
76 |
77 | )
78 | }
79 | handleNativeRequest(native) {
80 | this.setState({
81 | forcePoly : !native,
82 | filter : '',
83 | selectedOption : undefined
84 | })
85 | }
86 | onOptionSelected(option) {
87 | this.setState({
88 | selectedOption : option
89 | })
90 | }
91 | }
92 |
93 | export default App;
94 |
--------------------------------------------------------------------------------
/lib/components/DataList.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, '__esModule', {
4 | value: true
5 | });
6 |
7 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
8 |
9 | var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
10 |
11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
12 |
13 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
14 |
15 | function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
16 |
17 | var _react = require('react');
18 |
19 | var _react2 = _interopRequireDefault(_react);
20 |
21 | var _DataListOption = require('./DataListOption');
22 |
23 | var _DataListOption2 = _interopRequireDefault(_DataListOption);
24 |
25 | var DataList = (function (_React$Component) {
26 | _inherits(DataList, _React$Component);
27 |
28 | function DataList() {
29 | _classCallCheck(this, DataList);
30 |
31 | _get(Object.getPrototypeOf(DataList.prototype), 'constructor', this).apply(this, arguments);
32 | }
33 |
34 | _createClass(DataList, [{
35 | key: 'render',
36 | value: function render() {
37 | var _this = this;
38 |
39 | var options = this.props.options.map(function (option, index) {
40 | return _react2['default'].createElement(_DataListOption2['default'], {
41 | key: option + index,
42 | option: option,
43 | index: index,
44 | useNative: _this.props.useNative,
45 | selected: _this.props.selected == index,
46 | select: _this.props.select });
47 | });
48 | var containerStyle = {};
49 | if (!this.props.useNative) {
50 | if (this.props.hide) containerStyle.display = 'none';else if (this.props.options.length == 0) containerStyle.display = 'none';else containerStyle.display = 'block';
51 | }
52 | if (this.props.useNative) return _react2['default'].createElement(
53 | 'datalist',
54 | { id: this.props.id, className: "react-datalist" },
55 | options
56 | );
57 | return _react2['default'].createElement(
58 | 'div',
59 | { id: this.props.id, className: 'react-datalist', style: containerStyle },
60 | options
61 | );
62 | }
63 | }]);
64 |
65 | return DataList;
66 | })(_react2['default'].Component);
67 |
68 | exports['default'] = DataList;
69 | module.exports = exports['default'];
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React-datalist
2 |
3 | [](https://ci.testling.com/asbjornenge/react-datalist)
4 |
5 | React-datalist is an attempt at making a [\](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/datalist) polyfill as a reusable [react](http://facebook.github.io/react/) module.
6 |
7 | Feedback in the form of [issues](https://github.com/asbjornenge/react-datalist/issues) and [pull-requests](https://github.com/asbjornenge/react-datalist/pulls) is very much appreciated!
8 |
9 | Check out the [**DEMO**](http://www.asbjornenge.com/react-datalist/)
10 |
11 | **PS!** For use with react@0.12 or earlier, user react-datalist@1.3.1. 2.0.0 support react@0.13.0 and newer.
12 |
13 | ## Install
14 |
15 | npm install react-datalist
16 |
17 | ## Use
18 |
19 | var React = require('react')
20 | var ReactDatalist = require('react-datalist')
21 |
22 | var options = ['apple','orange','pear','pineapple','melon']
23 | React.render( , document.body)
24 |
25 | ## Props
26 |
27 | list * - and
28 | options * - the available options
29 | placeholder - a placeholder for the input field
30 | forcePoly - always use the polyfill (default false)
31 | blurTimeout - timeout after blur before hinding opts (default 200ms)
32 | autoPosition - automatically position the options list (default true)
33 | initialFilter - set the initial input value (default '')
34 | hideOptionsOnEsc - hide options on esc (default true)
35 | hideOptionsOnBlur - hide options on input blur (default true)
36 | includeLayoutStyle - include internal layout styling (style tag) (default true)
37 | onOptionSelected - callback triggered when option is considered selected
38 | getController - pass a function to collect a controller object (see below)
39 |
40 | * = required
41 |
42 | ### getController
43 |
44 | The getController property is there to enable external control of the component's inner state - while keeping the state in sync. It takes a function that will return a controller object.
45 |
46 | getController : function(controller) { /* ... */ }
47 |
48 | The controller offers the following
49 |
50 | controller.setFilter(string, callback) - sets the value of the input
51 | controller.toggleOptions(callback) - toggle show/hide of options. shown bool passed to callback.
52 | controller.getState() - gets the current inner state
53 | controller.setState(newState, callback) - set a new inner state
54 |
55 | ## !TLDR;
56 |
57 | React-datalist includes both a ***input*** and a ***datalist*** element. In order to stay fairly simple to use, align with react and avoid native events and other trickery, this was necessary. The structure is as follows:
58 |
59 | // Native
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | // Polyfill
69 |
70 |
76 |
77 | If you need to interact with the input element, attach listeners like **onInputChange**, **onInputBlur**, etc. (Note to self: expose additional input events. Note to others: encourage by creating issues)
78 |
79 | #### Styling
80 |
81 | There is also some (quite useful) styling you can/should use. You can find it under *node_modules/react-datalist/react-datalist.styl*. If you don't use stylus it's pretty small and easy to copy. I might include it if I pack up a UMD module for React-datalist. Anyone want that? Create an issue :-)
82 |
83 | (Note to self: Convert styling to plain css)
84 | (Idea: Should I pack a commonjs module that include the styling?)
85 |
86 | #### JSX
87 |
88 | The module itself does not make us of **JSX** as not to impose restrictions on the user.
89 |
90 | #### Features
91 |
92 | For a full feature list check out the [spec](https://github.com/asbjornenge/react-datalist/blob/master/test/spec.js).
93 |
94 | ## Changelog
95 |
96 | ### 4.0.0
97 |
98 | * Bumped React peerDependency major version to ^15.0.0 :tada: thanks to @dcousens
99 |
100 | ### 3.0.0
101 |
102 | * Support for React v0.14 :rocket:
103 | * Updated testdom which also updates jsdom wich in turn requires node 4+ :stuck_out_tongue_closed_eyes:
104 |
105 | ### 2.0.0
106 |
107 | * Support for React v0.13 :tada: :rocket:
108 |
109 | ### 1.3.1
110 |
111 | * Moved react to peerDependencies with >= instaed of ^
112 |
113 | ### 1.3.0
114 |
115 | * Added support for *props.className* to set extra classes (by [@blackbing](https://github.com/blackbing) [#11](https://github.com/asbjornenge/react-datalist/pull/11))
116 | * Added support for *props.defaultValue* to set default value (by [@blackbing](https://github.com/blackbing) [#11](https://github.com/asbjornenge/react-datalist/pull/11))
117 |
118 | ### 1.2.0
119 |
120 | * Added *hideOptionsOnEsc* for the ability to opt out of hiding options when user hits esc
121 | * Added *controller.getState* for a sneak peak at the inner state
122 | * Added *controller.setState* for improved external control
123 |
124 | ### 1.1.0
125 |
126 | * Bumped the blurTimeout up to 200ms (people were having issues)
127 | * Added the ability to specify *blurTimeout* via props
128 | * Added *hideOptionsOnBlur* for the ability to opt out of hide-options-on-blur
129 |
130 | ### 1.0.0
131 |
132 | * Removed setFilter support
133 | * Added a more generic getController prop that returns an object with functions for external control
134 | * Added setFilter to controller
135 | * Added toggleOptions to controller
136 |
137 | ### 0.2.0
138 |
139 | * Added support for externally controlling the filter state via the ***setFilter*** property.
140 |
141 | ### 0.1.2
142 |
143 | * Minor improvement to help testing (test are too fast for my timeouts :-P)
144 |
145 | ### 0.1.1
146 |
147 | * Increased hide-options timeout on blur (more time to make click register)
148 | * Supporting placeholder property for input
149 |
150 | ### 0.1.0
151 |
152 | * Added support for passing initial input value via the ***initialFilter*** property.
153 |
154 | ### 0.0.1
155 |
156 | * Initial release! YaY :-D
157 |
158 | enjoy.
159 |
--------------------------------------------------------------------------------
/src/ReactDataList.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import DataList from './components/DataList'
4 | import DataListOption from './components/DataListOption'
5 | import layout from './styles/react-datalist.styl'
6 |
7 | export default class ReactDataList extends React.Component {
8 | constructor(props) {
9 | super(props)
10 | this.state = {
11 | filter : props.initialFilter || props.defaultValue || '',
12 | hide : true,
13 | selected : false,
14 | support : !!('list' in document.createElement('input')) && !!(document.createElement('datalist') && window.HTMLDataListElement)
15 | }
16 | }
17 | render() {
18 | var options = this.filterOptions(this.props.options, this.state.filter, this.useNative())
19 | var extraClasses = this.props.className? ' ' + this.props.className: '';
20 | var layoutstyle = (this.props.includeLayoutStyle) ? : null
21 | return (
22 |
23 | {layoutstyle}
24 |
35 |
44 |
45 | )
46 | }
47 | handleInputBlur(event) {
48 | if (this.props.hideOptionsOnBlur) {
49 | setTimeout(function() {
50 | this.setState({ hide : true })
51 | }.bind(this),this.props.blurTimeout)
52 | }
53 | if (typeof this.props.onInputBlur === 'function') this.props.onInputBlur(event)
54 | }
55 | handleInputClick(event) {
56 | this.setState({ hide : false })
57 | }
58 | handleInputChange(event) {
59 | this.setState({
60 | filter : event.target.value,
61 | selected : false,
62 | hide : false
63 | })
64 | if (typeof this.props.onInputChange === 'function') this.props.onInputChange(event)
65 | }
66 | handleInputKeyDown(event) {
67 | switch(event.which) {
68 | case 40:
69 | // DOWN Arrow
70 | var newSelectedIndex = this.state.selected === false ? 0 : this.state.selected + 1
71 | var availableOptions = this.filterOptions(this.props.options, this.state.filter, this.useNative())
72 | if (newSelectedIndex >= availableOptions.length) newSelectedIndex = availableOptions.length - 1
73 | this.setState({
74 | selected : newSelectedIndex,
75 | hide : false
76 | })
77 | break
78 | case 38:
79 | // UP arrow
80 | var newSelectedIndex = this.state.selected > 0 ? this.state.selected - 1 : 0
81 | this.setState({selected : newSelectedIndex})
82 | break
83 | case 9:
84 | // TAB
85 | event.preventDefault();
86 | if (typeof this.state.selected === 'number') { this.selectFilteredOption(this.state.selected) }
87 | else { this.selectOption(event.target.value) }
88 | break
89 | case 13:
90 | // ENTER
91 | if (typeof this.state.selected === 'number') { this.selectFilteredOption(this.state.selected) }
92 | else { this.selectOption(event.target.value) }
93 | break
94 | }
95 | }
96 | handleInputKeyUp(event) {
97 | if (!this.props.hideOptionsOnEsc) return
98 | switch(event.which) {
99 | case 27:
100 | // ESC
101 | this.setState({
102 | selected : false,
103 | hide : true,
104 | filter : this.state.hide ? "" : this.state.filter
105 | })
106 | break
107 | }
108 | }
109 | filterOptions(options, filter, support) {
110 | if (support) return options
111 | if (!filter) return options
112 | if (filter === '') return options
113 | if (!options) return []
114 | return options.filter(function(option) {
115 | return option.toLowerCase().indexOf(filter.toLowerCase()) >= 0
116 | })
117 | }
118 | selectFilteredOption(index) {
119 | this.selectOption(this.filterOptions(this.props.options, this.state.filter, this.useNative())[index])
120 | }
121 | selectOption(value) {
122 | var selected_option;
123 | this.props.options.forEach(function(option, index) { if(option.toLowerCase() === value.toLowerCase()) selected_option = option })
124 | if (typeof selected_option === 'undefined') return
125 | if (typeof this.props.onOptionSelected === 'function') this.props.onOptionSelected(selected_option)
126 | this.setState({
127 | filter : selected_option,
128 | selected : false,
129 | hide : true
130 | })
131 | }
132 | useNative() {
133 | var _native = this.state.support
134 | if (this.props.forcePoly) _native = false
135 | return _native
136 | }
137 | componentWillMount() {
138 | if (typeof this.props.getController === 'function') {
139 | this.props.getController({
140 | setFilter : function(value,callback) { this.setState({filter : value}, callback) }.bind(this),
141 | toggleOptions : function(callback) { var hide = !this.state.hide; this.setState({filter : '', hide : hide}, function() { if (typeof callback === 'function') callback(!hide) }) }.bind(this),
142 | getState : function() { return {
143 | hide : this.state.hide,
144 | filter : this.state.filter,
145 | selected : this.state.selected,
146 | options : this.filterOptions(this.props.options, this.state.filter, this.useNative())
147 | }}.bind(this),
148 | setState : function(state,callback) { this.setState(state, callback) }.bind(this)
149 | })
150 | }
151 | }
152 | componentDidMount() {
153 | if (this.useNative()) return
154 | if (this.props.autoPosition === false) return
155 |
156 | /** POSITION **/
157 |
158 | setTimeout(function() {
159 | if (this.refs.theInput == undefined) return // <- Tests are too fast!
160 | if (this.refs.theDatalist == undefined) return // <- Tests are too fast!
161 | var _input = ReactDOM.findDOMNode(this.refs.theInput)
162 | var _datalist = ReactDOM.findDOMNode(this.refs.theDatalist)
163 | var pos = this.findPos(_input)
164 |
165 | _datalist.style.position = 'absolute'
166 | _datalist.style.top = pos[0] + _input.offsetHeight
167 | _datalist.style.left = pos[1]
168 | _datalist.style.width = (_input.offsetWidth - 2) + 'px'
169 | }.bind(this),50)
170 |
171 | }
172 | findPos(element) {
173 | if (element) {
174 | var parentPos = this.findPos(element.offsetParent);
175 | return [ parentPos[0] + element.offsetTop, parentPos[1] + element.offsetLeft]
176 | } else {
177 | return [0,0];
178 | }
179 | }
180 | }
181 | ReactDataList.defaultProps = {
182 | blurTimeout : 200,
183 | includeLayoutStyle : true,
184 | hideOptionsOnBlur : true,
185 | hideOptionsOnEsc : true
186 | }
187 |
188 |
--------------------------------------------------------------------------------
/lib/ReactDataList.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, '__esModule', {
4 | value: true
5 | });
6 |
7 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
8 |
9 | var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
10 |
11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
12 |
13 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
14 |
15 | function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
16 |
17 | var _react = require('react');
18 |
19 | var _react2 = _interopRequireDefault(_react);
20 |
21 | var _reactDom = require('react-dom');
22 |
23 | var _reactDom2 = _interopRequireDefault(_reactDom);
24 |
25 | var _componentsDataList = require('./components/DataList');
26 |
27 | var _componentsDataList2 = _interopRequireDefault(_componentsDataList);
28 |
29 | var _componentsDataListOption = require('./components/DataListOption');
30 |
31 | var _componentsDataListOption2 = _interopRequireDefault(_componentsDataListOption);
32 |
33 | var layout = '.react-datalist {\n margin: 0 !important;\n border: 1px solid #a1c1e7;\n max-height: 500px;\n overflow-x: scroll;\n background-color: #fff;\n}\n.react-datalist .react-datalist-option {\n display: block;\n margin: 0 !important;\n width: 94%;\n padding: 3%;\n cursor: pointer;\n}\n.react-datalist .react-datalist-option:hover {\n background-color: #bad4fe;\n}\n.react-datalist .react-datalist-option.react-datalist-option-selected {\n background-color: #bad4fe;\n}\n';
34 |
35 | var ReactDataList = (function (_React$Component) {
36 | _inherits(ReactDataList, _React$Component);
37 |
38 | function ReactDataList(props) {
39 | _classCallCheck(this, ReactDataList);
40 |
41 | _get(Object.getPrototypeOf(ReactDataList.prototype), 'constructor', this).call(this, props);
42 | this.state = {
43 | filter: props.initialFilter || props.defaultValue || '',
44 | hide: true,
45 | selected: false,
46 | support: !!('list' in document.createElement('input')) && !!(document.createElement('datalist') && window.HTMLDataListElement)
47 | };
48 | }
49 |
50 | _createClass(ReactDataList, [{
51 | key: 'render',
52 | value: function render() {
53 | var options = this.filterOptions(this.props.options, this.state.filter, this.useNative());
54 | var extraClasses = this.props.className ? ' ' + this.props.className : '';
55 | var layoutstyle = this.props.includeLayoutStyle ? _react2['default'].createElement(
56 | 'style',
57 | null,
58 | layout
59 | ) : null;
60 | return _react2['default'].createElement(
61 | 'div',
62 | { className: 'react-datalist-container' },
63 | layoutstyle,
64 | _react2['default'].createElement('input', { ref: 'theInput',
65 | list: this.props.list,
66 | value: this.state.filter,
67 | className: "react-datalist-input" + extraClasses,
68 | placeholder: this.props.placeholder,
69 | onBlur: this.handleInputBlur.bind(this),
70 | onKeyUp: this.handleInputKeyUp.bind(this),
71 | onClick: this.handleInputClick.bind(this),
72 | onChange: this.handleInputChange.bind(this),
73 | onKeyDown: this.handleInputKeyDown.bind(this)
74 | }),
75 | _react2['default'].createElement(_componentsDataList2['default'], { ref: 'theDatalist',
76 | id: this.props.list,
77 | hide: this.state.hide,
78 | filter: this.state.filter,
79 | select: this.selectFilteredOption.bind(this),
80 | options: options,
81 | selected: this.state.selected,
82 | useNative: this.useNative()
83 | })
84 | );
85 | }
86 | }, {
87 | key: 'handleInputBlur',
88 | value: function handleInputBlur(event) {
89 | if (this.props.hideOptionsOnBlur) {
90 | setTimeout((function () {
91 | this.setState({ hide: true });
92 | }).bind(this), this.props.blurTimeout);
93 | }
94 | if (typeof this.props.onInputBlur === 'function') this.props.onInputBlur(event);
95 | }
96 | }, {
97 | key: 'handleInputClick',
98 | value: function handleInputClick(event) {
99 | this.setState({ hide: false });
100 | }
101 | }, {
102 | key: 'handleInputChange',
103 | value: function handleInputChange(event) {
104 | this.setState({
105 | filter: event.target.value,
106 | selected: false,
107 | hide: false
108 | });
109 | if (typeof this.props.onInputChange === 'function') this.props.onInputChange(event);
110 | }
111 | }, {
112 | key: 'handleInputKeyDown',
113 | value: function handleInputKeyDown(event) {
114 | switch (event.which) {
115 | case 40:
116 | // DOWN Arrow
117 | var newSelectedIndex = this.state.selected === false ? 0 : this.state.selected + 1;
118 | var availableOptions = this.filterOptions(this.props.options, this.state.filter, this.useNative());
119 | if (newSelectedIndex >= availableOptions.length) newSelectedIndex = availableOptions.length - 1;
120 | this.setState({
121 | selected: newSelectedIndex,
122 | hide: false
123 | });
124 | break;
125 | case 38:
126 | // UP arrow
127 | var newSelectedIndex = this.state.selected > 0 ? this.state.selected - 1 : 0;
128 | this.setState({ selected: newSelectedIndex });
129 | break;
130 | case 9:
131 | // TAB
132 | event.preventDefault();
133 | if (typeof this.state.selected === 'number') {
134 | this.selectFilteredOption(this.state.selected);
135 | } else {
136 | this.selectOption(event.target.value);
137 | }
138 | break;
139 | case 13:
140 | // ENTER
141 | if (typeof this.state.selected === 'number') {
142 | this.selectFilteredOption(this.state.selected);
143 | } else {
144 | this.selectOption(event.target.value);
145 | }
146 | break;
147 | }
148 | }
149 | }, {
150 | key: 'handleInputKeyUp',
151 | value: function handleInputKeyUp(event) {
152 | if (!this.props.hideOptionsOnEsc) return;
153 | switch (event.which) {
154 | case 27:
155 | // ESC
156 | this.setState({
157 | selected: false,
158 | hide: true,
159 | filter: this.state.hide ? "" : this.state.filter
160 | });
161 | break;
162 | }
163 | }
164 | }, {
165 | key: 'filterOptions',
166 | value: function filterOptions(options, filter, support) {
167 | if (support) return options;
168 | if (!filter) return options;
169 | if (filter === '') return options;
170 | if (!options) return [];
171 | return options.filter(function (option) {
172 | return option.toLowerCase().indexOf(filter.toLowerCase()) >= 0;
173 | });
174 | }
175 | }, {
176 | key: 'selectFilteredOption',
177 | value: function selectFilteredOption(index) {
178 | this.selectOption(this.filterOptions(this.props.options, this.state.filter, this.useNative())[index]);
179 | }
180 | }, {
181 | key: 'selectOption',
182 | value: function selectOption(value) {
183 | var selected_option;
184 | this.props.options.forEach(function (option, index) {
185 | if (option.toLowerCase() === value.toLowerCase()) selected_option = option;
186 | });
187 | if (typeof selected_option === 'undefined') return;
188 | if (typeof this.props.onOptionSelected === 'function') this.props.onOptionSelected(selected_option);
189 | this.setState({
190 | filter: selected_option,
191 | selected: false,
192 | hide: true
193 | });
194 | }
195 | }, {
196 | key: 'useNative',
197 | value: function useNative() {
198 | var _native = this.state.support;
199 | if (this.props.forcePoly) _native = false;
200 | return _native;
201 | }
202 | }, {
203 | key: 'componentWillMount',
204 | value: function componentWillMount() {
205 | if (typeof this.props.getController === 'function') {
206 | this.props.getController({
207 | setFilter: (function (value, callback) {
208 | this.setState({ filter: value }, callback);
209 | }).bind(this),
210 | toggleOptions: (function (callback) {
211 | var hide = !this.state.hide;this.setState({ filter: '', hide: hide }, function () {
212 | if (typeof callback === 'function') callback(!hide);
213 | });
214 | }).bind(this),
215 | getState: (function () {
216 | return {
217 | hide: this.state.hide,
218 | filter: this.state.filter,
219 | selected: this.state.selected,
220 | options: this.filterOptions(this.props.options, this.state.filter, this.useNative())
221 | };
222 | }).bind(this),
223 | setState: (function (state, callback) {
224 | this.setState(state, callback);
225 | }).bind(this)
226 | });
227 | }
228 | }
229 | }, {
230 | key: 'componentDidMount',
231 | value: function componentDidMount() {
232 | if (this.useNative()) return;
233 | if (this.props.autoPosition === false) return;
234 |
235 | /** POSITION **/
236 |
237 | setTimeout((function () {
238 | if (this.refs.theInput == undefined) return; // <- Tests are too fast!
239 | if (this.refs.theDatalist == undefined) return; // <- Tests are too fast!
240 | var _input = _reactDom2['default'].findDOMNode(this.refs.theInput);
241 | var _datalist = _reactDom2['default'].findDOMNode(this.refs.theDatalist);
242 | var pos = this.findPos(_input);
243 |
244 | _datalist.style.position = 'absolute';
245 | _datalist.style.top = pos[0] + _input.offsetHeight;
246 | _datalist.style.left = pos[1];
247 | _datalist.style.width = _input.offsetWidth - 2 + 'px';
248 | }).bind(this), 50);
249 | }
250 | }, {
251 | key: 'findPos',
252 | value: function findPos(element) {
253 | if (element) {
254 | var parentPos = this.findPos(element.offsetParent);
255 | return [parentPos[0] + element.offsetTop, parentPos[1] + element.offsetLeft];
256 | } else {
257 | return [0, 0];
258 | }
259 | }
260 | }]);
261 |
262 | return ReactDataList;
263 | })(_react2['default'].Component);
264 |
265 | exports['default'] = ReactDataList;
266 |
267 | ReactDataList.defaultProps = {
268 | blurTimeout: 200,
269 | includeLayoutStyle: true,
270 | hideOptionsOnBlur: true,
271 | hideOptionsOnEsc: true
272 | };
273 | module.exports = exports['default'];
274 |
--------------------------------------------------------------------------------
/test/spec.js:
--------------------------------------------------------------------------------
1 | var dom = require('testdom')('
');
2 | var assert = require('assert');
3 | var _ = require('lodash');
4 | var React = require('react');
5 | var ReactDOM = require('react-dom');
6 | var nanodom = require('nanodom');
7 | var ReactDatalist = require('../src/ReactDataList');
8 | var ReactTestUtils = require('react-addons-test-utils');
9 |
10 | /** VARIABLES **/
11 |
12 | var ReactDatalistController;
13 | var getController = function(controller) { ReactDatalistController = controller }
14 | var options = ['apple','orange','pear','pineapple','melon']
15 | var defaultProps = {options:options, list:'fruit', forcePoly:true, getController:getController}
16 | var blurTimeout = 250
17 | var mountNode = document.querySelector("#app")
18 |
19 | /** HELPER FUNCTIONS **/
20 |
21 | function render(props, callback) {
22 | let _props = Object.assign(props, defaultProps)
23 | return ReactDOM.render( , mountNode, function() {
24 | if (typeof callback === 'function') setTimeout(callback)
25 | })
26 | }
27 | function setInputValue(datalist, value) {
28 | var __input = nanodom('.react-datalist-input')[0]
29 | var _input = ReactTestUtils.findRenderedDOMComponentWithTag(datalist, 'input')
30 | __input.value = value
31 | ReactTestUtils.Simulate.change(_input)
32 | }
33 | function clickOption(datalist, index) {
34 | var _options = ReactTestUtils.scryRenderedDOMComponentsWithClass(datalist, 'react-datalist-option')
35 | ReactTestUtils.Simulate.click(_options[index])
36 | }
37 |
38 | /** TESTS **/
39 |
40 | describe('DATALIST', function() {
41 |
42 | afterEach(function(done) {
43 | ReactDOM.unmountComponentAtNode(mountNode)
44 | mountNode.innerHTML = ""
45 | setTimeout(done)
46 | })
47 |
48 | it('Should render a container, an input and a datalist', function(done) {
49 | render({},function() {
50 | var __datalist = nanodom('.react-datalist')
51 | assert(__datalist.length == 1)
52 | var __container = nanodom('.react-datalist-container')
53 | assert(__container.length == 1)
54 | var __input = nanodom('.react-datalist-input')
55 | assert(__input.length == 1)
56 | done()
57 | })
58 | })
59 |
60 | it('Should be able to filter options', function(done) {
61 | var _datalist = render({},function() {
62 | var filtered = _datalist.filterOptions(options, null)
63 | assert(filtered.length === options.length)
64 | filtered = _datalist.filterOptions(options)
65 | assert(filtered.length === options.length)
66 | filtered = _datalist.filterOptions(options, "p")
67 | assert(filtered.length === 3)
68 | filtered = _datalist.filterOptions(options, "lon")
69 | assert(filtered.length === 1)
70 | done()
71 | })
72 | })
73 |
74 | it('Should support setting initial input value throught initialFilter property', function(done) {
75 | render({initialFilter : 'meh'}, function() {
76 | var __input = nanodom('.react-datalist-input')[0]
77 | assert(__input.value === 'meh')
78 | done()
79 | })
80 | })
81 |
82 | it('Should render a datalist with the passed options', function(done) {
83 | var _datalist = render({}, function() {
84 | setInputValue(_datalist, '')
85 | var domlist = nanodom('.react-datalist')
86 | assert(domlist.length == 1)
87 | assert(domlist[0].childNodes.length == options.length)
88 | assert(domlist[0].id == 'fruit')
89 | done()
90 | })
91 | })
92 |
93 | it('Filter options should change when the input value changes', function(done) {
94 | var _datalist = render({}, function() {
95 | var __datalist = nanodom('.react-datalist')[0]
96 | setInputValue(_datalist, '')
97 | assert(__datalist.childNodes.length == options.length)
98 | setInputValue(_datalist, 'melon')
99 | assert(__datalist.childNodes.length == 1)
100 | done()
101 | })
102 | })
103 |
104 | it('Should call its callback', function(done) {
105 | var onInputChange = function(event) {
106 | assert(true)
107 | done()
108 | }
109 | var _datalist = render({ onInputChange : onInputChange }, function() {
110 | setInputValue(_datalist, 'p')
111 | })
112 | })
113 |
114 | it('Can navigate options with arrow keys', function(done) {
115 | var _datalist = render({}, function() {
116 | setInputValue(_datalist, '')
117 | var __datalist = nanodom('.react-datalist')[0]
118 | var __input = nanodom('.react-datalist-input')[0]
119 | var _input = ReactTestUtils.findRenderedDOMComponentWithTag(_datalist, 'input')
120 | assert(__datalist.childNodes.length == options.length)
121 | // Down to first item
122 | ReactTestUtils.Simulate.keyDown(_input, {which: 40, type: "keydown"})
123 | var __option = nanodom('.react-datalist-option-selected')
124 | assert(__option.length == 1)
125 | assert(__option[0].innerHTML == options[0])
126 | // Down to second item
127 | ReactTestUtils.Simulate.keyDown(_input, {which: 40, type: "keydown"})
128 | var __option = nanodom('.react-datalist-option-selected')
129 | assert(__option.length == 1)
130 | assert(__option[0].innerHTML == options[1])
131 | // Up to first item
132 | ReactTestUtils.Simulate.keyDown(_input, {which: 38, type: "keydown"})
133 | var __option = nanodom('.react-datalist-option-selected')
134 | assert(__option.length == 1)
135 | assert(__option[0].innerHTML == options[0])
136 | done()
137 | })
138 | })
139 |
140 | it('Can select an option by hitting enter', function(done) {
141 | var onOptionSelected = function(option) {
142 | assert(option == options[0])
143 | done()
144 | }
145 | var _datalist = render({ onOptionSelected : onOptionSelected }, function() {
146 | setInputValue(_datalist, '')
147 | var __datalist = nanodom('.react-datalist')[0]
148 | var __input = nanodom('.react-datalist-input')[0]
149 | var _input = ReactTestUtils.findRenderedDOMComponentWithTag(_datalist, 'input')
150 | assert(__datalist.childNodes.length == options.length)
151 | // Down to an item
152 | ReactTestUtils.Simulate.keyDown(_input, {which: 40, type: "keydown"})
153 | var __option = nanodom('.react-datalist-option-selected')
154 | assert(__option.length == 1)
155 | // Enter to select it
156 | ReactTestUtils.Simulate.keyDown(_input, {which: 13, type: "keydown"})
157 | })
158 | })
159 |
160 | it('Can hide the options by pressing ESC', function(done) {
161 | var _datalist = render({}, function() {
162 | setInputValue(_datalist, 'p')
163 | var __datalist, __input, _input;
164 | _input = ReactTestUtils.findRenderedDOMComponentWithTag(_datalist, 'input')
165 | ReactTestUtils.Simulate.change(_input)
166 | __datalist = nanodom('.react-datalist')[0]
167 | assert(__datalist.childNodes.length == 3)
168 | // ESC to hide the options
169 | ReactTestUtils.Simulate.keyUp(_input, {which: 27, type: "keyup"})
170 | __datalist = nanodom('.react-datalist')[0]
171 | __input = nanodom('.react-datalist-input')[0]
172 | assert(__datalist.style.display === 'none')
173 | assert(__input.value === 'p')
174 | // ESC again to clear filter
175 | ReactTestUtils.Simulate.keyUp(_input, {which: 27, type: "keyup"})
176 | __datalist = nanodom('.react-datalist')[0]
177 | __input = nanodom('.react-datalist-input')[0]
178 | assert(__datalist.style.display === 'none')
179 | assert(__input.value === '')
180 | done()
181 | })
182 | })
183 |
184 | it('will not hide the options by pressing ESC if hideOptionsOnEsc=false is sendt as prop', function(done) {
185 | var _datalist = render({hideOptionsOnEsc:false}, function() {
186 | setInputValue(_datalist, 'p')
187 | var __datalist, __input, _input;
188 | _input = ReactTestUtils.findRenderedDOMComponentWithTag(_datalist, 'input')
189 | ReactTestUtils.Simulate.change(_input)
190 | __datalist = nanodom('.react-datalist')[0]
191 | assert(__datalist.childNodes.length == 3)
192 | // ESC to hide the options
193 | ReactTestUtils.Simulate.keyUp(_input, {which: 27, type: "keyup"})
194 | __datalist = nanodom('.react-datalist')[0]
195 | __input = nanodom('.react-datalist-input')[0]
196 | assert(__datalist.style.display === 'block')
197 | assert(__input.value === 'p')
198 | // ESC again to clear filter
199 | ReactTestUtils.Simulate.keyUp(_input, {which: 27, type: "keyup"})
200 | __datalist = nanodom('.react-datalist')[0]
201 | __input = nanodom('.react-datalist-input')[0]
202 | assert(__datalist.style.display === 'block')
203 | assert(__input.value === 'p')
204 | done()
205 | })
206 | })
207 |
208 | it('Will select an option if you click it', function(done) {
209 | var onOptionSelected = function(option) {
210 | assert(option == options[3])
211 | done()
212 | }
213 | var _datalist = render({filter:'', onOptionSelected:onOptionSelected}, function() {
214 | clickOption(_datalist, 3)
215 | })
216 | })
217 |
218 | it('Will releveal the available options if the input field is clicked', function(done) {
219 | var _datalist = render({}, function() {
220 | clickOption(_datalist, 0)
221 | var domlist = nanodom('.react-datalist')[0]
222 | assert(domlist.style.display === 'none')
223 | var _input = ReactTestUtils.findRenderedDOMComponentWithTag(_datalist, 'input')
224 | ReactTestUtils.Simulate.click(_input)
225 | var domlist = nanodom('.react-datalist')[0]
226 | assert(domlist.style.display === 'block')
227 | done()
228 | })
229 | })
230 |
231 | it('Will hide the options on input blur', function(done) {
232 | var _datalist = render({}, function() {
233 | setInputValue(_datalist, 'melon')
234 | var domlist = nanodom('.react-datalist')[0]
235 | assert(domlist.childNodes.length == 1)
236 | assert(domlist.style.display === 'block')
237 | var _input = ReactTestUtils.findRenderedDOMComponentWithTag(_datalist, 'input')
238 | ReactTestUtils.Simulate.blur(_input)
239 | setTimeout(function() {
240 | var domlist = nanodom('.react-datalist')[0]
241 | assert(domlist.childNodes.length == 1)
242 | assert(domlist.style.display === 'none')
243 | done()
244 | },blurTimeout)
245 | })
246 | })
247 |
248 | it('will NOT hide the options on input blur if hideOptionsOnInputBlur is false', function(done) {
249 | var _datalist = render({hideOptionsOnBlur:false}, function() {
250 | setInputValue(_datalist, 'melon')
251 | var domlist = nanodom('.react-datalist')[0]
252 | assert(domlist.childNodes.length == 1)
253 | assert(domlist.style.display === 'block')
254 | var _input = ReactTestUtils.findRenderedDOMComponentWithTag(_datalist, 'input')
255 | ReactTestUtils.Simulate.blur(_input)
256 | setTimeout(function() {
257 | var domlist = nanodom('.react-datalist')[0]
258 | assert(domlist.childNodes.length == 1)
259 | assert(domlist.style.display === 'block')
260 | done()
261 | },blurTimeout)
262 | })
263 | })
264 |
265 | it('Will expose the input blur event', function(done) {
266 | var blur = function(event) {
267 | assert(true)
268 | // Need to account for the timeout that in turn calles setState
269 | // if we don't it will try to setState on an unmounted component
270 | // and the test will crash.
271 | setTimeout(function() {
272 | done()
273 | }, blurTimeout)
274 | }
275 | var _datalist = render({ onInputBlur : blur }, function() {
276 | var _input = ReactTestUtils.findRenderedDOMComponentWithTag(_datalist, 'input')
277 | ReactTestUtils.Simulate.blur(_input)
278 | })
279 | })
280 |
281 | it('Can autoposition itself', function(done) {
282 | var _datalist = render({}, function() {
283 | setInputValue(_datalist, 'p')
284 | setTimeout(function() {
285 | var domlist = nanodom('.react-datalist')[0]
286 | var style = window.getComputedStyle(domlist)
287 | var position = style.getPropertyValue('position')
288 | assert(position == 'absolute')
289 | done()
290 | }, 100)
291 | })
292 | })
293 |
294 |
295 | it('Can take a placeholder property for the input', function(done) {
296 | render({ placeholder : 'Choose project' }, function() {
297 | var __input = nanodom('.react-datalist-input')[0]
298 | assert(__input.attributes.placeholder.value === 'Choose project')
299 | done()
300 | })
301 | })
302 |
303 | it('exposes a function for controlling the input state externally', function(done) {
304 | render({}, function() {
305 | assert(ReactDatalistController != undefined)
306 | var __input = nanodom('.react-datalist-input')[0]
307 | assert(__input.value != 'poop')
308 | ReactDatalistController.setFilter('poop')
309 | assert(__input.value == 'poop')
310 | done()
311 | })
312 | })
313 |
314 | it('exposes a function for toggling option visibility externally', function(done) {
315 | render({}, function() {
316 | assert(ReactDatalistController != undefined)
317 | var __input = nanodom('.react-datalist-input')[0]
318 | assert(__input.value != 'poop')
319 | ReactDatalistController.setFilter('poop')
320 | assert(__input.value == 'poop')
321 | var __options = nanodom('.react-datalist-option')
322 | assert(__options.length == 0)
323 | ReactDatalistController.toggleOptions(function(shown) {
324 | assert(shown)
325 | __input = nanodom('.react-datalist-input')[0]
326 | __options = nanodom('.react-datalist-option')
327 | var __datalist = nanodom('.react-datalist')[0]
328 | assert(__input.value == '')
329 | assert(__options.length == options.length)
330 | assert(__datalist.style.display == 'block')
331 | ReactDatalistController.toggleOptions(function(shown) {
332 | assert(!shown)
333 | __datalist = nanodom('.react-datalist')[0]
334 | assert(__datalist.style.display == 'none')
335 | done()
336 | })
337 | })
338 | })
339 |
340 | })
341 |
342 | it('includes layout style by default', function(done) {
343 | render({}, function() {
344 | var style = nanodom('style')
345 | assert(style.length == 1)
346 | done()
347 | })
348 | })
349 |
350 | it('does not include layout style if includeLayoutStyle=false is passed as prop', function(done) {
351 | render({ includeLayoutStyle : false }, function() {
352 | var style = nanodom('style')
353 | assert(style.length == 0)
354 | done()
355 | })
356 | })
357 |
358 | })
359 |
--------------------------------------------------------------------------------