From 1a4633468d362dad3f0de6b6b0407fc850c87ad9 Mon Sep 17 00:00:00 2001 From: John O'Sullivan Date: Fri, 6 Dec 2019 13:17:44 -0500 Subject: [PATCH 01/10] Updated README to reflect our fork --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 30bac79..3ff0325 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,14 @@ -# Ink Quicksearch +# Ink 2 Quicksearch -> QuickSearch Component for [Ink](https://github.com/vadimdemedes/ink) +> QuickSearch Component for [Ink 2](https://github.com/vadimdemedes/ink) + +> Forked from the origiinal [`ink-quicksearch`](https://github.com/aicioara/ink-quicksearch) to upgrade it to Ink 2. -![CircleCI](https://circleci.com/gh/aicioara/ink-quicksearch.png?style=shield&circle-token=:circle-token) ## Install ``` -$ npm install ink-quicksearch +$ npm install ink2-quicksearch ``` ## Quickstart From 9aab07e0063a136968176690c2b9d9b8c79b1dbe Mon Sep 17 00:00:00 2001 From: John O'Sullivan Date: Mon, 9 Dec 2019 13:11:43 -0500 Subject: [PATCH 02/10] Did some renaming, finished moving all examples over, updated package.json to specify files when published --- .circleci/config.yml | 37 -- .eslintrc.js | 36 -- .gitignore | 1 + README.md | 45 +- bin/release.sh | 7 - dist/index.js | 280 --------- examples/example1.js | 1 - examples/example1.jsx | 32 - examples/example2.js | 1 - examples/example2.jsx | 39 -- examples/example3.js | 1 - examples/example3.jsx | 45 -- examples/example4.js | 1 - examples/example5.js | 1 - examples/example5.jsx | 48 -- package-lock.json | 594 ++++++++++++++++++ package.json | 62 +- src/QuickSearch.jsx | 259 -------- src/QuickSearchInput.tsx | 290 +++++++++ src/examples/Example1.tsx | 33 + src/examples/Example2.tsx | 37 ++ src/examples/Example3.tsx | 49 ++ .../example4.jsx => src/examples/Example4.tsx | 40 +- src/examples/ExampleDirectory.tsx | 39 ++ tsconfig.json | 31 + 25 files changed, 1149 insertions(+), 860 deletions(-) delete mode 100644 .circleci/config.yml delete mode 100644 .eslintrc.js delete mode 100755 bin/release.sh delete mode 100644 dist/index.js delete mode 100644 examples/example1.js delete mode 100644 examples/example1.jsx delete mode 100644 examples/example2.js delete mode 100644 examples/example2.jsx delete mode 100644 examples/example3.js delete mode 100644 examples/example3.jsx delete mode 100644 examples/example4.js delete mode 100644 examples/example5.js delete mode 100644 examples/example5.jsx create mode 100644 package-lock.json delete mode 100644 src/QuickSearch.jsx create mode 100644 src/QuickSearchInput.tsx create mode 100644 src/examples/Example1.tsx create mode 100644 src/examples/Example2.tsx create mode 100644 src/examples/Example3.tsx rename examples/example4.jsx => src/examples/Example4.tsx (76%) create mode 100644 src/examples/ExampleDirectory.tsx create mode 100644 tsconfig.json diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index e19cc43..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,37 +0,0 @@ -# Javascript Node CircleCI 2.0 configuration file -# -# Check https://circleci.com/docs/2.0/language-javascript/ for more details -# -version: 2 -jobs: - build: - docker: - # specify the version you desire here - - image: circleci/node:7.10 - - # Specify service dependencies here if necessary - # CircleCI maintains a library of pre-built images - # documented at https://circleci.com/docs/2.0/circleci-images/ - # - image: circleci/mongo:3.4.4 - - working_directory: ~/repo - - steps: - - checkout - - # Download and cache dependencies - - restore_cache: - keys: - - v1-dependencies-{{ checksum "package.json" }} - # fallback to using the latest cache if no exact match is found - - v1-dependencies- - - - run: yarn install - - - save_cache: - paths: - - node_modules - key: v1-dependencies-{{ checksum "package.json" }} - - # run tests! - - run: yarn run eslint diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index e3a9e21..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,36 +0,0 @@ -module.exports = { - "env": { - "es6": true, - "node": true - }, - "extends": [ - "eslint:recommended", - "plugin:react/recommended" - ], - "parserOptions": { - "ecmaFeatures": { - "experimentalObjectRestSpread": true, - "jsx": true - }, - "sourceType": "module" - }, - "plugins": [ - "react" - ], - "rules": { - "indent": ["error", 4], - "linebreak-style": ["error", "unix"], - "quotes": ["error", "single"], - "semi": ["error", "always"], - "no-console": "off", - - // React - "react/prop-types": "off", // This is ink - "react/no-deprecated": "off", // This is ink - }, - "settings": { - "react": { - "pragma": "h", - } - } -}; diff --git a/.gitignore b/.gitignore index e1c92c7..3a114f1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # IDE .idea +.vscode # Build build/ diff --git a/README.md b/README.md index 3ff0325..5fe877c 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,21 @@ -# Ink 2 Quicksearch +# ink-quicksearch-input > QuickSearch Component for [Ink 2](https://github.com/vadimdemedes/ink) -> Forked from the origiinal [`ink-quicksearch`](https://github.com/aicioara/ink-quicksearch) to upgrade it to Ink 2. +Forked from the origiinal [`ink-quicksearch`](https://github.com/aicioara/ink-quicksearch) to upgrade it to Ink 2. Big thanks to @aicioara for laying out the core logic! The re-write uses modern function +components and hooks. It is also in Typescript, improving the developer experience. ## Install ``` -$ npm install ink2-quicksearch +$ npm install ink-quicksearch-input ``` ## Quickstart +If you'd like to get a feel for how the component works, you can see the examples in action by running: + ```bash npm install npm start @@ -21,13 +24,18 @@ npm start ## Usage ```jsx -const {h, render, Component} = require('ink'); -const QuickSearch = require('ink-quicksearch'); - -class Demo extends Component { - render() { - const props = { - items: [ +import React, { useState } from 'react'; +import { render, Text } from 'ink'; +import { QuickSearchInput } from 'ink-quicksearch-input'; + +const Demo = (props) => { + const [result, setResult] = useState(''); + return ( + <> + The user selected {result}. + {'\n'} + { - // `item` = { label: 'First', value: 'first' } - };, - }; - - return - } + // ... + ]} + onSelect={(item) => setResult(item.label)} /> + + ) } render(); @@ -61,10 +66,11 @@ render(); | forceMatchingQuery | `bool` | `false` | If set to true, queries that return no results are not allowed. In particular, if previous query `X` returns at least one result and `X + new_character` would not, query will not update to `X + new_character`. | clearQueryChars | `Array(char)` | `['\u0015', '\u0017']`
(Ctrl + u, Ctrl + w) | Key Combinations that will clear the query.
`ch` follows the `keypress` API `process.stdin.on('keypress', (ch, key) => {})`. | initialSelectionIndex | `int` | `0` | Selection index when the component is initially rendered or when `props.items` changes. Can be set together with new `props.items` to automatically select an option. +| label | `string` | | Optionally provide a label which will appear before the current query. | indicatorComponent | Component | | Custom component to override the default indicator component (default - arrow). | itemComponent | Component | | Custom component to override the default item style (default - selection coloring). | highlightComponent | Component | | Custom component to override the default highlight style (default - background highlight). -| statusComponent | Component | | Custom component to override the status component (default - current query). +| statusComponent | Component | | Custom component to override the status component (default - current query, optional value label). ## Component Props @@ -107,6 +113,7 @@ Props: - `hasMatch`: `boolean` - `children`: `any` +- `label`: Optional `string` diff --git a/bin/release.sh b/bin/release.sh deleted file mode 100755 index c9734ea..0000000 --- a/bin/release.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash - -THIS_DIR=$(dirname "$0") -cd ${THIS_DIR} -cd .. - - diff --git a/dist/index.js b/dist/index.js deleted file mode 100644 index b296b40..0000000 --- a/dist/index.js +++ /dev/null @@ -1,280 +0,0 @@ -const { h, Component, Color } = require('ink'); -const hasAnsi = require('has-ansi'); -const isEqual = require('lodash.isequal'); - -const defaultValue = { label: '' }; // Used as return for empty array - - -// For the following four, whitespace is important -const IndicatorComponent = ({ isSelected }) => { - return h( - Color, - { hex: '#00FF00' }, - isSelected ? '>' : ' ', - ' ' - ); -}; - -const ItemComponent = ({ isSelected, children }) => h( - Color, - { hex: isSelected ? '#00FF00' : '' }, - children -); - -const HighlightComponent = ({ children }) => h( - Color, - { bgHex: '#6C71C4' }, - children -); - -const StatusComponent = ({ hasMatch, children }) => h( - Color, - { hex: hasMatch ? '#00FF00' : '#FF0000' }, - children -); - -class QuickSearch extends Component { - constructor(props) { - super(props); - this.state = QuickSearch.initialState; - this.state.selectionIndex = this.props.initialSelectionIndex; - this.handleKeyPress = this.handleKeyPress.bind(this); - } - - render() { - // Cannot have these starting with lowercases - const HighlightComponent_ = this.props.highlightComponent; - const ItemComponent_ = this.props.itemComponent; - const IndicatorComponent_ = this.props.indicatorComponent; - const StatusComponent_ = this.props.statusComponent; - - const begin = this.state.startIndex; - let end = this.props.items.length; - if (this.props.limit !== 0) { - end = Math.min(begin + this.props.limit, this.props.items.length); - } - const items = this.props.items.slice(begin, end); - - const rows = items.map((item, index) => { - const isLast = index === items.length - 1; - const isSelected = index + this.state.startIndex === this.state.selectionIndex; - const isHighlighted = undefined; - - const itemProps = { isSelected, isHighlighted, item }; - - const label = item.label; - const queryPosition = this.getMatchPosition(label, this.state.query); - - let labelComponent = ''; - if (queryPosition === -1) { - itemProps.isHighlighted = false; - labelComponent = h( - 'span', - null, - label - ); - } else { - itemProps.isHighlighted = true; - const start = queryPosition; - const end = start + this.state.query.length; - - const first = label.slice(0, start); - const second = label.slice(start, end); - const third = label.slice(end); - - labelComponent = h( - 'span', - null, - first, - h( - HighlightComponent_, - itemProps, - second - ), - third - ); - } - - return h( - ItemComponent_, - Object.assign({ key: item.value }, itemProps), - h(IndicatorComponent_, itemProps), - labelComponent, - !isLast && h('br', null) - ); - }); - - return h( - 'span', - null, - rows, - h( - StatusComponent_, - { hasMatch: this.state.hasMatch }, - h('br', null), - this.state.query - ) - ); - } - - componentDidMount() { - process.stdin.on('keypress', this.handleKeyPress); - } - - componentWillUnmount() { - process.stdin.removeListener('keypress', this.handleKeyPress); - } - - componentWillReceiveProps(nextProps) { - if (!isEqual(this.props.items, nextProps.items)) { - this.setState(QuickSearch.initialState); - if (nextProps.initialSelectionIndex != null) { - this._updateSelectionIndex(nextProps.initialSelectionIndex, nextProps); - } - } - } - - handleKeyPress(ch, key) { - if (!this.props.focus) { - return; - } - - if (this.props.clearQueryChars.indexOf(ch) !== -1) { - this.setState({ query: '' }); - } else if (key.name === 'return') { - this.props.onSelect(this.getValue()); - } else if (key.name === 'backspace') { - this._updateQuery(this.state.query.slice(0, -1)); - } else if (key.name === 'up') { - this._changeSelection(-1); - } else if (key.name === 'down') { - this._changeSelection(1); - } else if (key.name === 'tab') { - if (key.shift === false) { - this._changeSelection(1); - } else { - this._changeSelection(-1); - } - } else if (key.name === 'pageup' || key.name === 'pagedown') { - this._handlePageChange(key.name); - } else if (hasAnsi(key.sequence)) { - // No-op - } else { - this._updateQuery(this.state.query + ch); - } - } - - _updateQuery(query) { - let selectionIndex = this.state.selectionIndex; - let hasMatch = false; - if (query.trim() === '' || this.getMatchPosition(this.getValue().label, query) !== -1) { - hasMatch = true; - } else { - for (var i = 0; i < this.props.items.length; i++) { - if (this.getMatchPosition(this.props.items[i].label, query) !== -1) { - selectionIndex = i; - hasMatch = true; - break; - } - } - } - - if (!hasMatch && this.props.forceMatchingQuery) { - return; - } - - this._updateSelectionIndex(selectionIndex); - this.setState({ query, hasMatch }); - } - - _changeSelection(delta) { - for (let selectionIndex = this.state.selectionIndex + delta; 0 <= selectionIndex && selectionIndex < this.props.items.length; selectionIndex += delta) { - if (!this.state.hasMatch) { - this._updateSelectionIndex(selectionIndex); - break; - } - - if (this.getMatchPosition(this.props.items[selectionIndex].label, this.state.query) !== -1) { - this._updateSelectionIndex(selectionIndex); - break; - } - } - } - - _updateSelectionIndex(selectionIndex, props) { - if (props == undefined) { - props = this.props; - } - this.setState({ selectionIndex }); - if (props.limit === 0) { - return; - } - const begin = this.state.startIndex; - const end = Math.min(begin + props.limit, props.items.length); - if (begin <= selectionIndex && selectionIndex < end) { - return; - } else if (selectionIndex >= end) { - if (selectionIndex >= props.items.length) { - throw Error(`Error: selection index (${selectionIndex}) outside items range (${props.items.length}).`); - } - const startIndex = selectionIndex - props.limit + 1; - this.setState({ startIndex }); - } else { - // if (selectionIndex < begin) - this.setState({ startIndex: selectionIndex }); - } - } - - _handlePageChange(keyName) { - if (this.state.query.trim() !== '') { - return; // Do not page when selecting - } - if (this.props.limit === 0) { - return; // Nothing to page - } - let newIndex = this.state.selectionIndex; - if (keyName === 'pageup') { - newIndex = Math.max(this.state.selectionIndex - this.props.limit + 1, 0); - } else if (keyName === 'pagedown') { - newIndex = Math.min(this.state.selectionIndex + this.props.limit - 1, this.props.items.length - 1); - } - this._changeSelection(newIndex - this.state.selectionIndex); - } - - getMatchPosition(label, query) { - if (this.props.caseSensitive) { - return label.indexOf(query); - } else { - return label.toLowerCase().indexOf(query.toLowerCase()); - } - } - - getValue() { - return this.props.items[this.state.selectionIndex] || defaultValue; - } -} - -QuickSearch.initialState = { - query: '', - hasMatch: true, - selectionIndex: 0, - startIndex: 0 -}; - -QuickSearch.defaultProps = { - items: [], - onSelect: () => {}, // no-op - focus: true, - caseSensitive: false, - limit: 0, - forceMatchingQuery: false, - clearQueryChars: ['\u0015', // Ctrl + U - '\u0017'], - initialSelectionIndex: 0, - indicatorComponent: IndicatorComponent, - itemComponent: ItemComponent, - highlightComponent: HighlightComponent, - statusComponent: StatusComponent -}; - -module.exports = QuickSearch; diff --git a/examples/example1.js b/examples/example1.js deleted file mode 100644 index 0a23ab1..0000000 --- a/examples/example1.js +++ /dev/null @@ -1 +0,0 @@ -require('import-jsx')('./example1.jsx'); diff --git a/examples/example1.jsx b/examples/example1.jsx deleted file mode 100644 index fd87ee6..0000000 --- a/examples/example1.jsx +++ /dev/null @@ -1,32 +0,0 @@ -/** - * This is the basic usage example, including all defaults - */ - -const {h, render, Color, Component} = require('ink'); - -const QuickSearch = require('import-jsx')('../src/QuickSearch.jsx'); - -class Example1 extends Component { - render() { - const props = { - items: [ - {value: 1, label: 'Animal'}, - {value: 3, label: 'Antilope'}, - {value: 2, label: 'Animation'}, - {value: 0, label: 'Animate'}, - {value: 4, label: 'Arizona'}, - {value: 5, label: 'Aria'}, - {value: 6, label: 'Arid'}, - ], - onSelect: d => console.log('You selected', d), - }; - - return - Example 1 -
- -
; - } -} - -render(); diff --git a/examples/example2.js b/examples/example2.js deleted file mode 100644 index 2644218..0000000 --- a/examples/example2.js +++ /dev/null @@ -1 +0,0 @@ -require('import-jsx')('./example2.jsx'); diff --git a/examples/example2.jsx b/examples/example2.jsx deleted file mode 100644 index b81ad97..0000000 --- a/examples/example2.jsx +++ /dev/null @@ -1,39 +0,0 @@ -/** - * This is a example that does not feature a search box and - * is case sensitive - */ - -const {h, render, Color, Component} = require('ink'); - -const QuickSearch = require('import-jsx')('../src/QuickSearch.jsx'); - - -const StatusComponent = () => ; // No-op - - -class Example2 extends Component { - render() { - const props = { - items: [ - {label: 'Animal'}, - {label: 'Antilope'}, - {label: 'Animation'}, - {label: 'Animate'}, - {label: 'Arizona'}, - {label: 'Aria'}, - {label: 'Arid'}, - ], - onSelect: d => console.log(d), - caseSensitive: true, - statusComponent: StatusComponent, - }; - - return - Example 2 -
- -
; - } -} - -render(); diff --git a/examples/example3.js b/examples/example3.js deleted file mode 100644 index 1ecb56e..0000000 --- a/examples/example3.js +++ /dev/null @@ -1 +0,0 @@ -require('import-jsx')('./example3.jsx'); diff --git a/examples/example3.jsx b/examples/example3.jsx deleted file mode 100644 index 5988c58..0000000 --- a/examples/example3.jsx +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Example with disappearing options - */ - -const {h, render, Color, Component} = require('ink'); - -const QuickSearch = require('import-jsx')('../src/QuickSearch.jsx'); - - -const ItemComponent = ({isHighlighted, isSelected, children}) => { - if (!isHighlighted) { - return ; - } - return {children}; -}; - -const StatusComponent = () => ; // No-op - - -class Example3 extends Component { - render() { - const props = { - items: [ - {label: 'Animal'}, - {label: 'Antilope'}, - {label: 'Animation'}, - {label: 'Animate'}, - {label: 'Arizona'}, - {label: 'Aria'}, - {label: 'Arid'}, - ], - onSelect: d => console.log(d), - itemComponent: ItemComponent, - statusComponent: StatusComponent, - }; - - return - Example 3 -
- -
; - } -} - -render(); diff --git a/examples/example4.js b/examples/example4.js deleted file mode 100644 index 402cd7f..0000000 --- a/examples/example4.js +++ /dev/null @@ -1 +0,0 @@ -require('import-jsx')('./example4.jsx'); diff --git a/examples/example5.js b/examples/example5.js deleted file mode 100644 index fda2f17..0000000 --- a/examples/example5.js +++ /dev/null @@ -1 +0,0 @@ -require('import-jsx')('./example5.jsx'); diff --git a/examples/example5.jsx b/examples/example5.jsx deleted file mode 100644 index 325c94f..0000000 --- a/examples/example5.jsx +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Using initialSelectionIndex. Press Enter to cycle between states - */ - -const {h, render, Color, Component} = require('ink'); - -const QuickSearch = require('import-jsx')('../src/QuickSearch.jsx'); - -class Example1 extends Component { - constructor(props) { - super(props); - this.state = {setIndex: 0}; - } - - render() { - const sets = [ - [ - {label: 'Aardvark'}, - {label: 'Abyssinian'}, - {label: 'Adelie Penguin'}, - {label: 'Affenpinscher'}, // Selected - {label: 'Afghan Hound'}, - ], - [ - {label: 'Baboon'}, - {label: 'Bactrian Camel'}, // Selected - {label: 'Badger'}, - {label: 'Balinese'}, - {label: 'Banded Palm Civet'}, - ] - ]; - const selection = [3, 1]; - - const props = { - items: sets[this.state.setIndex], - onSelect: () => this.setState({setIndex: this.state.setIndex ^ 1}), - initialSelectionIndex: selection[this.state.setIndex], - }; - - return - Example 5 -
- -
; - } -} - -render(); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..98bb6c8 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,594 @@ +{ + "name": "ink-quicksearch-input", + "version": "0.3.2", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true + }, + "@types/has-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/has-ansi/-/has-ansi-3.0.0.tgz", + "integrity": "sha512-H3vFOwfLlFEC0MOOrcSkus8PCnMCzz4N0EqUbdJZCdDhBTfkAu86aRYA+MTxjKW6jCpUvxcn4715US8g+28BMA==", + "dev": true + }, + "@types/ink": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/ink/-/ink-2.0.3.tgz", + "integrity": "sha512-DYKIKEJqhsGfQ/jgX0t9BzfHmBJ/9dBBT2MDsHAQRAfOPhEe7LZm5QeNBx1J34/e108StCPuJ3r4bh1y38kCJA==", + "dev": true, + "requires": { + "ink": "*" + } + }, + "@types/lodash": { + "version": "4.14.149", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.149.tgz", + "integrity": "sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ==", + "dev": true + }, + "@types/lodash.isequal": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/@types/lodash.isequal/-/lodash.isequal-4.5.5.tgz", + "integrity": "sha512-4IKbinG7MGP131wRfceK6W4E/Qt3qssEFLF30LnJbjYiSfHGGRU/Io8YxXrZX109ir+iDETC8hw8QsDijukUVg==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, + "@types/node": { + "version": "12.12.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.14.tgz", + "integrity": "sha512-u/SJDyXwuihpwjXy7hOOghagLEV1KdAST6syfnOk6QZAMzZuWZqXy5aYYZbh8Jdpd4escVFP0MvftHNDb9pruA==", + "dev": true + }, + "@types/prop-types": { + "version": "15.7.3", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", + "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==", + "dev": true + }, + "@types/react": { + "version": "16.9.15", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.15.tgz", + "integrity": "sha512-WsmM1b6xQn1tG3X2Hx4F3bZwc2E82pJXt5OPs2YJgg71IzvUoKOSSSYOvLXYCg1ttipM+UuA4Lj3sfvqjVxyZw==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "csstype": "^2.2.0" + } + }, + "ansi-escapes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.0.tgz", + "integrity": "sha512-EiYhwo0v255HUL6eDyuLrXEkTi7WwVCLAw+SeOQ7M7qdun1z1pum4DEm/nuqIVbPvi9RPPc9k9LbyBv6H0DwVg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "ansi-styles": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.0.tgz", + "integrity": "sha512-7kFQgnEaMdRtwf6uSfUnVr9gSGC7faurn+J/Mv90/W+iTtN0405/nLdopfMWwchyxhbGYl6TC4Sccn9TUkGAgg==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "dev": true + }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, + "auto-bind": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-3.0.0.tgz", + "integrity": "sha512-v0A231a/lfOo6kxQtmEkdBfTApvC21aJYukA8pkKnoTvVqh3Wmm7/Rwy4GBCHTTHVoLVA5qsBDDvf1XY1nIV2g==", + "dev": true + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "requires": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "csstype": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.7.tgz", + "integrity": "sha512-9Mcn9sFbGBAdmimWb2gLVDtFJzeKtDGIr76TUqmjZrw9LFXBMSU70lcs+C0/7fyCd6iBDqmksUcCOUIkisPHsQ==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "has-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-3.0.0.tgz", + "integrity": "sha1-Ngd+8dFfMzSEqn+neihgbxxlWzc=", + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "ink": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/ink/-/ink-2.6.0.tgz", + "integrity": "sha512-nD/wlSuB6WnFsFB0nUcOJdy28YvvDer3eo+gezjvZqojGA4Rx5sQpacvN//Aai83DRgwrRTyKBl5aciOcfP3zQ==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "arrify": "^2.0.1", + "auto-bind": "^3.0.0", + "chalk": "^3.0.0", + "cli-cursor": "^3.1.0", + "cli-truncate": "^2.0.0", + "is-ci": "^2.0.0", + "lodash.throttle": "^4.1.1", + "log-update": "^3.0.0", + "prop-types": "^15.6.2", + "react-reconciler": "^0.24.0", + "scheduler": "^0.18.0", + "signal-exit": "^3.0.2", + "slice-ansi": "^3.0.0", + "string-length": "^3.1.0", + "widest-line": "^3.1.0", + "wrap-ansi": "^6.2.0", + "yoga-layout-prebuilt": "^1.9.3" + } + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "keypress": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/keypress/-/keypress-0.2.1.tgz", + "integrity": "sha1-HoBFQlABjbrUw/6USX1uZ7YmnHc=" + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" + }, + "lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=", + "dev": true + }, + "log-update": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-3.3.0.tgz", + "integrity": "sha512-YSKm5n+YjZoGZT5lfmOqasVH1fIH9xQA9A81Y48nZ99PxAP62vdCCtua+Gcu6oTn0nqtZd/LwRV+Vflo53ZDWA==", + "dev": true, + "requires": { + "ansi-escapes": "^3.2.0", + "cli-cursor": "^2.1.0", + "wrap-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true + }, + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + } + } + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "onetime": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "dev": true, + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + }, + "react": { + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.12.0.tgz", + "integrity": "sha512-fglqy3k5E+81pA8s+7K0/T3DBCF0ZDOher1elBFzF7O6arXJgzyu/FW+COxFvAWXJoJN9KIZbT2LXlukwphYTA==", + "dev": true, + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2" + } + }, + "react-is": { + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz", + "integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==", + "dev": true + }, + "react-reconciler": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.24.0.tgz", + "integrity": "sha512-gAGnwWkf+NOTig9oOowqid9O0HjTDC+XVGBCAmJYYJ2A2cN/O4gDdIuuUQjv8A4v6GDwVfJkagpBBLW5OW9HSw==", + "dev": true, + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.18.0" + } + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "scheduler": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.18.0.tgz", + "integrity": "sha512-agTSHR1Nbfi6ulI0kYNK0203joW2Y5W4po4l+v03tOoiJKpTBbxpNhWDvqc/4IcOw+KLmSiQLTasZ4cab2/UWQ==", + "dev": true, + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + }, + "string-length": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-3.1.0.tgz", + "integrity": "sha512-Ttp5YvkGm5v9Ijagtaz1BnN+k9ObpvS0eIBblPMp2YWL8FBmi9qblQ9fexc2k/CXFgrTIteU3jAw3payCnwSTA==", + "dev": true, + "requires": { + "astral-regex": "^1.0.0", + "strip-ansi": "^5.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + } + } + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "term-size": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.1.0.tgz", + "integrity": "sha512-I42EWhJ+2aeNQawGx1VtpO0DFI9YcfuvAMNIdKyf/6sRbHJ4P+ZQ/zIT87tE+ln1ymAGcCJds4dolfSAS0AcNg==", + "dev": true + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, + "typescript": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.3.tgz", + "integrity": "sha512-Mcr/Qk7hXqFBXMN7p7Lusj1ktCBydylfQM/FZCk5glCNQJrCUKPkMHdo9R0MTFWsC/4kPFvDS0fDPvukfCkFsw==", + "dev": true + }, + "widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "dev": true, + "requires": { + "string-width": "^4.0.0" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "yoga-layout-prebuilt": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/yoga-layout-prebuilt/-/yoga-layout-prebuilt-1.9.3.tgz", + "integrity": "sha512-9SNQpwuEh2NucU83i2KMZnONVudZ86YNcFk9tq74YaqrQfgJWO3yB9uzH1tAg8iqh5c9F5j0wuyJ2z72wcum2w==", + "dev": true + } + } +} diff --git a/package.json b/package.json index ce5926d..11b8e00 100644 --- a/package.json +++ b/package.json @@ -1,58 +1,52 @@ { - "name": "ink-quicksearch", - "version": "0.3.2", - "description": "Quicksearch Input Component for Ink", - "main": "dist/index.js", + "name": "ink-quicksearch-input", + "version": "1.0.0", + "description": "Quicksearch Input Component for Ink 2", + "main": "build/QuickSearchInput.js", + "types": "src/QuickSearchInput.tsx", "author": { - "name": "Andrei Cioara", - "email": "github@andreicioara.com", - "url": "http://andrei.cioara.me" + "name": "John O'Sullivan " }, "license": "MIT", "scripts": { - "build": "babel src/QuickSearch.jsx --out-file=dist/index.js", - "eslint": "eslint src/**/*.jsx examples/**/*.jsx", - "eslint-fix": "eslint --fix src/**/*.jsx examples/**/*.jsx", - "start": "node examples/example1.js", - "test": "echo \"Error: no test specified\" && exit 1" + "build": "tsc", + "dev": "tsc --watch", + "start": "node build/examples/ExampleDirectory.js" }, + "files": [ + "src/QuickSearchInput.tsx", + "build/QuickSearchInput.js", + "tsconfig.json" + ], "repository": { "type": "git", - "url": "git+https://github.com/aicioara/ink-quicksearch.git" + "url": "git+https://github.com/Eximchain/ink-quicksearch-input.git" }, "keywords": [ "ink", "ink-component" ], "bugs": { - "url": "https://github.com/aicioara/ink-quicksearch/issues" + "url": "https://github.com/Eximchain/ink-quicksearch-input/issues" }, - "homepage": "https://github.com/aicioara/ink-quicksearch#readme", + "homepage": "https://github.com/Eximchain/ink-quicksearch-input#readme", "dependencies": { "has-ansi": "3.0.0", + "keypress": "^0.2.1", "lodash.isequal": "4.5.0" }, "devDependencies": { - "babel-cli": "6.24.1", - "babel-plugin-transform-react-jsx": "6.24.1", - "eslint": "4.19.1", - "eslint-plugin-react": "7.8.2", - "import-jsx": "1.3.0", - "ink": "0.5.0", - "term-size": "1.2.0" + "@types/has-ansi": "^3.0.0", + "@types/ink": "^2.0.3", + "@types/lodash.isequal": "^4.5.5", + "@types/node": "^12.12.14", + "@types/react": "^16.9.15", + "ink": "^2.6.0", + "react": "^16.12.0", + "term-size": "^2.1.0", + "typescript": "^3.7.3" }, "peerDependencies": { - "ink": "0.5.x" - }, - "babel": { - "plugins": [ - [ - "transform-react-jsx", - { - "pragma": "h", - "useBuiltIns": true - } - ] - ] + "ink": "2.6.x" } } diff --git a/src/QuickSearch.jsx b/src/QuickSearch.jsx deleted file mode 100644 index 9579b96..0000000 --- a/src/QuickSearch.jsx +++ /dev/null @@ -1,259 +0,0 @@ -const {h, Component, Color} = require('ink'); -const hasAnsi = require('has-ansi'); -const isEqual = require('lodash.isequal'); - -const defaultValue = {label:''}; // Used as return for empty array - - -// For the following four, whitespace is important -const IndicatorComponent = ({isSelected}) => { - return {isSelected ? '>' : ' '} ; -}; - -const ItemComponent = ({isSelected, children}) => ( - {children} -); - -const HighlightComponent = ({children}) => ( - {children} -); - -const StatusComponent = ({hasMatch, children}) => ( - {children} -); - - -class QuickSearch extends Component { - constructor(props) { - super(props); - this.state = QuickSearch.initialState; - this.state.selectionIndex = this.props.initialSelectionIndex; - this.handleKeyPress = this.handleKeyPress.bind(this); - } - - render() { - // Cannot have these starting with lowercases - const HighlightComponent_ = this.props.highlightComponent; - const ItemComponent_ = this.props.itemComponent; - const IndicatorComponent_ = this.props.indicatorComponent; - const StatusComponent_ = this.props.statusComponent; - - const begin = this.state.startIndex; - let end = this.props.items.length; - if (this.props.limit !== 0) { - end = Math.min(begin + this.props.limit, this.props.items.length); - } - const items = this.props.items.slice(begin, end); - - const rows = items.map((item, index) => { - const isLast = (index === items.length - 1); - const isSelected = (index + this.state.startIndex === this.state.selectionIndex); - const isHighlighted = undefined; - - const itemProps = {isSelected, isHighlighted, item}; - - const label = item.label; - const queryPosition = this.getMatchPosition(label, this.state.query); - - let labelComponent = ''; - if (queryPosition === -1) { - itemProps.isHighlighted = false; - labelComponent = {label}; - } else { - itemProps.isHighlighted = true; - const start = queryPosition; - const end = start + this.state.query.length; - - const first = label.slice(0, start); - const second = label.slice(start, end); - const third = label.slice(end); - - labelComponent = - {first} - {second} - {third} - ; - } - - return - - {labelComponent} - {!isLast &&
} -
; - }); - - return - {rows} - -
- {this.state.query} -
-
; - } - - componentDidMount() { - process.stdin.on('keypress', this.handleKeyPress); - } - - componentWillUnmount() { - process.stdin.removeListener('keypress', this.handleKeyPress); - } - - componentWillReceiveProps(nextProps) { - if (!isEqual(this.props.items, nextProps.items)) { - this.setState(QuickSearch.initialState); - if (nextProps.initialSelectionIndex != null) { - this._updateSelectionIndex(nextProps.initialSelectionIndex, nextProps); - } - } - } - - handleKeyPress(ch, key) { - if (!this.props.focus) { - return; - } - - if (this.props.clearQueryChars.indexOf(ch) !== -1) { - this.setState({query: ''}); - } else if (key.name === 'return') { - this.props.onSelect(this.getValue()); - } else if (key.name === 'backspace') { - this._updateQuery(this.state.query.slice(0, -1)); - } else if (key.name === 'up') { - this._changeSelection(-1); - } else if (key.name === 'down') { - this._changeSelection(1); - } else if (key.name === 'tab') { - if (key.shift === false) { - this._changeSelection(1); - } else { - this._changeSelection(-1); - } - } else if (key.name === 'pageup' || key.name === 'pagedown') { - this._handlePageChange(key.name); - } else if (hasAnsi(key.sequence)) { - // No-op - } else { - this._updateQuery(this.state.query + ch); - } - } - - _updateQuery(query) { - let selectionIndex = this.state.selectionIndex; - let hasMatch = false; - if (query.trim() === '' || this.getMatchPosition(this.getValue().label, query) !== -1) { - hasMatch = true; - } else { - for (var i = 0; i < this.props.items.length; i++) { - if (this.getMatchPosition(this.props.items[i].label, query) !== -1) { - selectionIndex = i; - hasMatch = true; - break; - } - } - } - - if (!hasMatch && this.props.forceMatchingQuery) { - return; - } - - this._updateSelectionIndex(selectionIndex); - this.setState({query, hasMatch}); - } - - _changeSelection(delta) { - for ( - let selectionIndex = this.state.selectionIndex + delta; - 0 <= selectionIndex && selectionIndex < this.props.items.length; - selectionIndex += delta - ) { - if (!this.state.hasMatch) { - this._updateSelectionIndex(selectionIndex); - break; - } - - if (this.getMatchPosition(this.props.items[selectionIndex].label, this.state.query) !== -1) { - this._updateSelectionIndex(selectionIndex); - break; - } - } - } - - _updateSelectionIndex(selectionIndex, props) { - if (props == undefined) { - props = this.props; - } - this.setState({selectionIndex}); - if (props.limit === 0) { - return; - } - const begin = this.state.startIndex; - const end = Math.min(begin + props.limit, props.items.length); - if (begin <= selectionIndex && selectionIndex < end) { - return; - } else if (selectionIndex >= end) { - if (selectionIndex >= props.items.length) { - throw Error(`Error: selection index (${selectionIndex}) outside items range (${props.items.length}).`); - } - const startIndex = selectionIndex - props.limit + 1; - this.setState({startIndex}); - } else { // if (selectionIndex < begin) - this.setState({startIndex: selectionIndex}); - } - } - - _handlePageChange(keyName) { - if (this.state.query.trim() !== '') { - return; // Do not page when selecting - } - if (this.props.limit === 0) { - return; // Nothing to page - } - let newIndex = this.state.selectionIndex; - if (keyName === 'pageup') { - newIndex = Math.max(this.state.selectionIndex - this.props.limit + 1, 0); - } else if (keyName === 'pagedown') { - newIndex = Math.min(this.state.selectionIndex + this.props.limit - 1, this.props.items.length - 1); - } - this._changeSelection(newIndex - this.state.selectionIndex); - } - - getMatchPosition(label, query) { - if (this.props.caseSensitive) { - return label.indexOf(query); - } else { - return label.toLowerCase().indexOf(query.toLowerCase()); - } - } - - getValue() { - return this.props.items[this.state.selectionIndex] || defaultValue; - } -} - -QuickSearch.initialState = { - query: '', - hasMatch: true, - selectionIndex: 0, - startIndex: 0, -}; - -QuickSearch.defaultProps = { - items: [], - onSelect: () => {}, // no-op - focus: true, - caseSensitive: false, - limit: 0, - forceMatchingQuery: false, - clearQueryChars: [ - '\u0015', // Ctrl + U - '\u0017', // Ctrl + W - ], - initialSelectionIndex: 0, - indicatorComponent: IndicatorComponent, - itemComponent: ItemComponent, - highlightComponent: HighlightComponent, - statusComponent: StatusComponent, -}; - -module.exports = QuickSearch; diff --git a/src/QuickSearchInput.tsx b/src/QuickSearchInput.tsx new file mode 100644 index 0000000..14c566a --- /dev/null +++ b/src/QuickSearchInput.tsx @@ -0,0 +1,290 @@ +import React, { Component, FunctionComponent, FC, useState, useEffect, useRef, useMemo, PropsWithChildren } from 'react'; +import { Color, useStdin, Text, Box } from 'ink'; +import hasAnsi from 'has-ansi'; +import isEqual from 'lodash.isequal'; +// @ts-ignore This module makes stdin emit keypress events, +// that's it. Hasn't been published in six years, no types +// available. +import keypress from 'keypress'; + +const defaultValue = { label: '' }; // Used as return for empty array + + +export type IsSelected = PropsWithChildren<{ + isSelected: boolean +}> + +export interface ItemProps extends IsSelected { + item: Item + isHighlighted: boolean | undefined +} +// For the following four, whitespace is important +const IndicatorComponent: FC = ({ isSelected }) => { + return {isSelected ? '>' : ' '} ; +}; + +const ItemComponent: FC = ({ isSelected, children }) => ( + {children} +); + +const HighlightComponent: FC = ({ children }) => ( + {children} +); + +export interface StatusProps { + hasMatch: boolean + label?: string +} + +const StatusComponent: FC = ({ hasMatch, children, label }) => ( +{ `${label || 'Query'}: ` }{children} +); + +export interface Item { + label: string + value?: string | number +} + +interface KeyPress { + name: string + sequence: string + shift: boolean +} + +export interface QuickSearchProps { + onSelect: (item: Item) => void + items: Item[] + label?: string + focus?: boolean + caseSensitive?: boolean + limit?: number + forceMatchingQuery?: boolean + clearQueryChars?: string[] + initialSelectionIndex?: number + indicatorComponent?: FunctionComponent + itemComponent?: FunctionComponent + highlightComponent?: FunctionComponent + statusComponent?: FunctionComponent +} + +export const QuickSearch: FC = (props) => { + const { + items, onSelect, focus, clearQueryChars, limit, + indicatorComponent, itemComponent, highlightComponent, + statusComponent, label, forceMatchingQuery + } = Object.assign({}, defaultProps, props); + + // Map prop components onto capitalized names, required + // for JSX to recognize em + const Indicator = indicatorComponent; + const Item = itemComponent; + const Highlight = highlightComponent; + const Status = statusComponent; + + const [windowIndices, setWindowIndices] = useState({ + selection: 0, + start: 0 + }) + const [query, setQuery] = useState(''); + + const matchingItems = useMemo(() => { + return getMatchingItems(); + }, [items, query]); + const usingLimitedView = limit !== 0 && matchingItems.length > limit; + + const inkStdin = useStdin(); + useEffect(function listenToRawKeyboard() { + keypress(inkStdin.stdin); + if (inkStdin.isRawModeSupported) inkStdin.setRawMode(true); + inkStdin.stdin.addListener('keypress', handleKeyPress) + return () => { + inkStdin.stdin.removeListener('keypress', handleKeyPress); + if (inkStdin.isRawModeSupported) inkStdin.setRawMode(false); + } + }, [inkStdin, query, items, windowIndices]) + + const itemRef = useRef(items); + useEffect(function resetForNewItems() { + if (!isEqual(items, itemRef.current)) { + itemRef.current = items; + setWindowIndices({ + selection: 0, start: 0 + }) + setQuery(''); + } + }, [items]) + + const getValue = () => { + return matchingItems[windowIndices.selection] || defaultValue; + } + + + function getMatchIndex(label: string, query: string) { + return props.caseSensitive ? + label.indexOf(query) : + label.toLowerCase().indexOf(query.toLowerCase()) + } + + function getMatchingItems(alternateQuery?: string) { + const matchQuery = alternateQuery || query; + if (matchQuery === '') return items; + return items.filter(item => getMatchIndex(item.label, matchQuery) >= 0); + } + + function removeCharFromQuery() { + setQuery((query) => query.slice(0, -1) as string) + } + + function addCharToQuery(newChar: string) { + setQuery((query) => { + let newQuery = query + newChar; + let newMatching = getMatchingItems(newQuery); + if (newMatching.length === 0 && forceMatchingQuery) { + return query; + } else { + setWindowIndices({ start: 0, selection: 0 }) + return newQuery + } + }) + } + + function selectUp() { + setWindowIndices((windowIndices) => { + const { selection, start } = windowIndices; + let newSelection = selection; + let newStart = start; + if (selection === 0) { + // Wrap around to the bottom + newSelection = matchingItems.length - 1; + if (usingLimitedView) { + newStart = matchingItems.length - limit; + } + } else { + // Go up, potentially moving up window, unless + // it is already 0. + newSelection -= 1; + if (usingLimitedView) { + if (selection - start <= 1 && start > 0 ) { + newStart -= 1; + } + } + } + return { + selection: newSelection, + start: newStart + } + }) + } + + function selectDown() { + setWindowIndices(({ start, selection }) => { + let newStart = start; + let newSelection = selection; + if (selection === matchingItems.length - 1) { + // Wrap around to the top + newSelection = 0; + if (newStart !== 0) newStart = 0; + } else { + // Go down, potentially moving window + newSelection++; + if (limit && matchingItems.length > limit && newSelection - newStart >= limit - 1) { + newStart += 1; + } + } + return { + start: newStart, + selection: newSelection + } + }) + } + + function handleKeyPress(ch: string, key: KeyPress) { + if (!focus) return; + if (clearQueryChars.indexOf(ch) !== -1) { + setQuery(''); + } else if (key.name === 'return') { + onSelect(getValue()); + } else if (key.name === 'backspace') { + removeCharFromQuery(); + } else if (key.name === 'up') { + selectUp(); + } else if (key.name === 'down') { + selectDown(); + } else if (key.name === 'tab') { + if (key.shift === false) { + selectDown(); + } else { + selectUp(); + } + } else if (hasAnsi(key.sequence)) { + // Ignore fancy Ansi escape codes + } else { + addCharToQuery(ch); + } + } + + const begin = windowIndices.start; + let end = items.length; + if (limit !== 0) end = Math.min(begin + limit, items.length); + const visibleItems = matchingItems.slice(begin, end); + + return ( + + + 0}> + {query} + + + { + visibleItems.length === 0 ? + + No matches : + + visibleItems.map((item) => { + const isSelected = matchingItems.indexOf(item) === windowIndices.selection; + const isHighlighted = undefined; + const itemProps: ItemProps = { isSelected, isHighlighted, item }; + const label = item.label; + + const queryStart = getMatchIndex(label, query); + const queryEnd = queryStart + query.length; + let labelComponent; + itemProps.isHighlighted = true; + const preMatch = label.slice(0, queryStart); + const match = label.slice(queryStart, queryEnd); + const postMatch = label.slice(queryEnd); + labelComponent = ( + {preMatch}{match}{postMatch} + ) + return ( + + + + {labelComponent} + + + ) + }) + } + + ) +} + +const defaultProps = { + focus: true, + caseSensitive: false, + limit: 0, + forceMatchingQuery: true, + clearQueryChars: [ + '\u0015', // Ctrl + U + '\u0017', // Ctrl + W + ], + initialSelectionIndex: 0, + indicatorComponent: IndicatorComponent, + itemComponent: ItemComponent, + highlightComponent: HighlightComponent, + statusComponent: StatusComponent +}; + + +export default QuickSearch; \ No newline at end of file diff --git a/src/examples/Example1.tsx b/src/examples/Example1.tsx new file mode 100644 index 0000000..2faeaa0 --- /dev/null +++ b/src/examples/Example1.tsx @@ -0,0 +1,33 @@ +import React, { FC, useState } from 'react'; +import { render, Color, Text, Box, Static } from 'ink'; + +import QuickSearch, { Item } from '../QuickSearchInput'; + +export const Example1Name = 'Basic'; + +export const Example1: FC = () => { + const [selectedValue, setSelectedValue] = useState(''); + return ( + <> + Example 1: {Example1Name} + Selected item is {selectedValue} + {'\n\n'} + setSelectedValue(item.label), + }} /> + + ) +} + +if (require.main && require.main.filename === __filename) { + render(); +} \ No newline at end of file diff --git a/src/examples/Example2.tsx b/src/examples/Example2.tsx new file mode 100644 index 0000000..161cb7c --- /dev/null +++ b/src/examples/Example2.tsx @@ -0,0 +1,37 @@ +import React, { FC, useState } from 'react'; +import { render, Color, Text, Box, Static } from 'ink'; + +import QuickSearch from '../QuickSearchInput'; + +export const Example2Name = 'Case-Sensitive, No Query/Status Element'; + +export const Example2: FC = () => { + const [selectedValue, setSelectedValue] = useState(''); + return ( + <> + Example 2: {Example2Name} + Selected item is {selectedValue} + { '\n\n' } + setSelectedValue(d.label), + caseSensitive: true, + // Hide the statusComponent + statusComponent: () => <>, + }} /> + + ) + +} + +if (require.main && require.main.filename === __filename) { + render(); +} \ No newline at end of file diff --git a/src/examples/Example3.tsx b/src/examples/Example3.tsx new file mode 100644 index 0000000..de66e89 --- /dev/null +++ b/src/examples/Example3.tsx @@ -0,0 +1,49 @@ +import React, { FC, useState } from 'react'; +import { render, Color, Text, Box, Static } from 'ink'; + +import QuickSearch, { ItemProps } from '../QuickSearchInput'; + +export const Example3Name = 'Case-Sensitive, Hiding Status & non-selected Items'; + +// Hide elements which are not selected +const ItemComponent = ({isHighlighted, isSelected, children}:ItemProps) => { + if (!isHighlighted) { + return <>; + } + return {children}; +}; + +const StatusComponent = () => <>; // No-op + +export const Example3: FC = () => { + const [selectedValue, setSelectedValue] = useState(''); + return ( + <> + Example 3: {Example3Name} + Selected item is {selectedValue} + { '\n\n' } + setSelectedValue(d.label), + caseSensitive: true, + // Hide the statusComponent + statusComponent: StatusComponent, + // Only show items which are selected + itemComponent: ItemComponent + }} /> + + ) + +} + +if (require.main && require.main.filename === __filename) { + render(); +} \ No newline at end of file diff --git a/examples/example4.jsx b/src/examples/Example4.tsx similarity index 76% rename from examples/example4.jsx rename to src/examples/Example4.tsx index 9f220d7..ec0a200 100644 --- a/examples/example4.jsx +++ b/src/examples/Example4.tsx @@ -1,15 +1,18 @@ -/** - * Limiting rows to current terminal size - */ +import React, { FC, useState } from 'react'; +import { render, Color, Text } from 'ink'; +import termSize from 'term-size'; +import QuickSearch from '../QuickSearchInput'; -const {h, render, Component} = require('ink'); -const termSize = require('term-size'); +export const Example4Name = 'Long list limited to terminal size; non-matching queries are not allowed'; -const QuickSearch = require('import-jsx')('../src/QuickSearch.jsx'); - -class Example3 extends Component { - render() { - const props = { +export const Example4: FC = () => { + const [selectedValue, setSelectedValue] = useState(''); + return ( + <> + Example 4: {Example4Name} + Selected item is {selectedValue} + { '\n\n' } + {}, - statusComponent: () => , + onSelect: d => setSelectedValue(d.label), + statusComponent: () => <>, forceMatchingQuery: true, limit: termSize().rows - 2, // One for clear screen, one for cursor (Could be 1 more for statusComponent if that exists) - }; + }} /> + + ) - return - - ; - } } -console.log('\x1Bc'); // Clear screen -render(); +if (require.main && require.main.filename === __filename) { + render(); +} \ No newline at end of file diff --git a/src/examples/ExampleDirectory.tsx b/src/examples/ExampleDirectory.tsx new file mode 100644 index 0000000..5bbf10d --- /dev/null +++ b/src/examples/ExampleDirectory.tsx @@ -0,0 +1,39 @@ +import React, { FC, useState } from 'react'; +import { render, Color, Text, Box, Static } from 'ink'; +import QuickSearch, { Item } from '../QuickSearchInput'; +import { Example1, Example1Name } from './Example1'; +import { Example2, Example2Name } from './Example2'; +import { Example3, Example3Name } from './Example3'; +import { Example4, Example4Name } from './Example4'; + +const ExampleDirectory: FC = () => { + const [selectedExample, setSelectedExample] = useState(''); + + if (selectedExample === Example1Name) { + return + } else if (selectedExample === Example2Name) { + return + } else if (selectedExample === Example3Name) { + return + } else if (selectedExample === Example4Name) { + return + } else { + return ( + <> + Which example would you like to explore? + {'\n\n'} + setSelectedExample(item.label), + }} /> + + ) + } +} + +render(); \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..81d939f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,31 @@ +{ + "include": [ + "src/**/*" + ], + "exclude": ["node_modules", "build"], + "compileOnSave": true, + "compilerOptions": { + "target": "es5", // Compatible with older browsers + "module": "commonjs", // Compatible with both Node.js and browser + "moduleResolution": "node", // Tell tsc to look in node_modules for modules + "sourceMap": false, // Creates *.js.map files + "inlineSourceMap": true, + "strict": true, // Strict types, eg. prohibits `var x=0; x=null` + "alwaysStrict": true, // Enable JavaScript's "use strict" mode + + "outDir": "build", + "sourceRoot": "./src", + "noImplicitAny": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "typeRoots": ["node_modules/@types"], + "jsx": "react" + }, + "typedocOptions": { + "mode" : "modules", + "out" : "docs/build", + "categorizeByGroup" : true, + "theme" : "docs/themeoverride", + "excludeNotExported" : false + } +} \ No newline at end of file From e21ca5d58c47bf7742c3378b2a9ccb434371775a Mon Sep 17 00:00:00 2001 From: John O'Sullivan Date: Mon, 9 Dec 2019 13:17:57 -0500 Subject: [PATCH 03/10] Updating README to give more credit to the original author; adding num visible indicator when limit is active --- README.md | 3 +-- src/QuickSearchInput.tsx | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5fe877c..ef773ca 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,7 @@ > QuickSearch Component for [Ink 2](https://github.com/vadimdemedes/ink) -Forked from the origiinal [`ink-quicksearch`](https://github.com/aicioara/ink-quicksearch) to upgrade it to Ink 2. Big thanks to @aicioara for laying out the core logic! The re-write uses modern function -components and hooks. It is also in Typescript, improving the developer experience. +Forked from [@aicioara](https://github.com/aicioara)'s original [`ink-quicksearch`](https://github.com/aicioara/ink-quicksearch) component to upgrade it to Ink 2. Big thanks to him for laying out the original logic in v1! If you are looking for a component that works with Ink v1, that's where to go. This re-write uses modern React (e.g. function components and hooks), and it is also in Typescript, improving the developer experience. ## Install diff --git a/src/QuickSearchInput.tsx b/src/QuickSearchInput.tsx index 14c566a..711a40b 100644 --- a/src/QuickSearchInput.tsx +++ b/src/QuickSearchInput.tsx @@ -37,7 +37,7 @@ export interface StatusProps { } const StatusComponent: FC = ({ hasMatch, children, label }) => ( -{ `${label || 'Query'}: ` }{children} + {`${label || 'Query'}: `}{children} ); export interface Item { @@ -113,7 +113,7 @@ export const QuickSearch: FC = (props) => { setQuery(''); } }, [items]) - + const getValue = () => { return matchingItems[windowIndices.selection] || defaultValue; } @@ -164,15 +164,15 @@ export const QuickSearch: FC = (props) => { // it is already 0. newSelection -= 1; if (usingLimitedView) { - if (selection - start <= 1 && start > 0 ) { + if (selection - start <= 1 && start > 0) { newStart -= 1; } } } - return { + return { selection: newSelection, start: newStart - } + } }) } @@ -266,6 +266,13 @@ export const QuickSearch: FC = (props) => { ) }) } + { + !usingLimitedView ? null : ( + + Viewing {begin}-{end} of {matchingItems.length} matching items + + ) + } ) } From 171325ce56a8964ef78e67e507a935f36a22c388 Mon Sep 17 00:00:00 2001 From: John O'Sullivan Date: Mon, 9 Dec 2019 13:28:50 -0500 Subject: [PATCH 04/10] Cleaned up Example4 and the numMatching indicator --- src/QuickSearchInput.tsx | 4 ++-- src/examples/Example4.tsx | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/QuickSearchInput.tsx b/src/QuickSearchInput.tsx index 711a40b..dd4ba83 100644 --- a/src/QuickSearchInput.tsx +++ b/src/QuickSearchInput.tsx @@ -230,7 +230,7 @@ export const QuickSearch: FC = (props) => { return ( - + 0}> {query} @@ -269,7 +269,7 @@ export const QuickSearch: FC = (props) => { { !usingLimitedView ? null : ( - Viewing {begin}-{end} of {matchingItems.length} matching items + Viewing {begin}-{end} of {matchingItems.length} matching items ({items.length} items overall) ) } diff --git a/src/examples/Example4.tsx b/src/examples/Example4.tsx index ec0a200..d0bbb15 100644 --- a/src/examples/Example4.tsx +++ b/src/examples/Example4.tsx @@ -11,7 +11,7 @@ export const Example4: FC = () => { <> Example 4: {Example4Name} Selected item is {selectedValue} - { '\n\n' } + { '\n' } { {label: 'Aye Aye'}, ], onSelect: d => setSelectedValue(d.label), - statusComponent: () => <>, forceMatchingQuery: true, - limit: termSize().rows - 2, // One for clear screen, one for cursor (Could be 1 more for statusComponent if that exists) + limit: termSize().rows - 8, // One for clear screen, one for cursor (Could be 1 more for statusComponent if that exists) }} /> ) From 36e8a11635562808de7da5bce8d96b2037fff91b Mon Sep 17 00:00:00 2001 From: John O'Sullivan Date: Mon, 9 Dec 2019 14:12:56 -0500 Subject: [PATCH 05/10] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ef773ca..88b3b36 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ > QuickSearch Component for [Ink 2](https://github.com/vadimdemedes/ink) -Forked from [@aicioara](https://github.com/aicioara)'s original [`ink-quicksearch`](https://github.com/aicioara/ink-quicksearch) component to upgrade it to Ink 2. Big thanks to him for laying out the original logic in v1! If you are looking for a component that works with Ink v1, that's where to go. This re-write uses modern React (e.g. function components and hooks), and it is also in Typescript, improving the developer experience. +Forked from [@aicioara](https://github.com/aicioara)'s original [`ink-quicksearch`](https://github.com/aicioara/ink-quicksearch) component to upgrade it to Ink 2. Big thanks to him for laying out the original logic in v1! If you are looking for a component that works with Ink v1, that's where to go. This re-write uses modern React (e.g. function components and hooks), and it is also in Typescript, improving the developer experience. The only behavioral difference is that this component always filters out items which do not match the query. ## Install From 06f55842bfa1065395cff6d76d9f1bd0bd3664a9 Mon Sep 17 00:00:00 2001 From: John O'Sullivan Date: Mon, 9 Dec 2019 15:44:56 -0500 Subject: [PATCH 06/10] Moving around dependencies to try and make things behave --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 11b8e00..8fe212c 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ }, "license": "MIT", "scripts": { + "prepare": "npm run build", "build": "tsc", "dev": "tsc --watch", "start": "node build/examples/ExampleDirectory.js" @@ -31,14 +32,14 @@ }, "homepage": "https://github.com/Eximchain/ink-quicksearch-input#readme", "dependencies": { + "@types/has-ansi": "^3.0.0", + "@types/lodash.isequal": "^4.5.5", "has-ansi": "3.0.0", "keypress": "^0.2.1", "lodash.isequal": "4.5.0" }, "devDependencies": { - "@types/has-ansi": "^3.0.0", "@types/ink": "^2.0.3", - "@types/lodash.isequal": "^4.5.5", "@types/node": "^12.12.14", "@types/react": "^16.9.15", "ink": "^2.6.0", From 0067cc77ba217f4da9ad83ded53887919a51064f Mon Sep 17 00:00:00 2001 From: John O'Sullivan Date: Mon, 9 Dec 2019 16:10:09 -0500 Subject: [PATCH 07/10] Playing with keys to try and get proper re-render behavior --- src/QuickSearchInput.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/QuickSearchInput.tsx b/src/QuickSearchInput.tsx index dd4ba83..19fcf20 100644 --- a/src/QuickSearchInput.tsx +++ b/src/QuickSearchInput.tsx @@ -229,7 +229,7 @@ export const QuickSearch: FC = (props) => { const visibleItems = matchingItems.slice(begin, end); return ( - + 0}> {query} @@ -257,7 +257,7 @@ export const QuickSearch: FC = (props) => { {preMatch}{match}{postMatch} ) return ( - + {labelComponent} From b815aa3375cd5d5d3a7b00c2af67d9a7ed1d5246 Mon Sep 17 00:00:00 2001 From: John O'Sullivan Date: Mon, 9 Dec 2019 16:37:59 -0500 Subject: [PATCH 08/10] Figured out number key issue --- src/QuickSearchInput.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/QuickSearchInput.tsx b/src/QuickSearchInput.tsx index 19fcf20..523733e 100644 --- a/src/QuickSearchInput.tsx +++ b/src/QuickSearchInput.tsx @@ -200,6 +200,10 @@ export const QuickSearch: FC = (props) => { function handleKeyPress(ch: string, key: KeyPress) { if (!focus) return; + if (!key && parseInt(ch) !== NaN) { + addCharToQuery(ch); + return; + } if (clearQueryChars.indexOf(ch) !== -1) { setQuery(''); } else if (key.name === 'return') { From 14663b7a068b3204a25eb5c2226ae03b4c99d9a8 Mon Sep 17 00:00:00 2001 From: John O'Sullivan Date: Mon, 9 Dec 2019 17:00:49 -0500 Subject: [PATCH 09/10] Highlighting query rather than label, emphasizing user --- src/QuickSearchInput.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/QuickSearchInput.tsx b/src/QuickSearchInput.tsx index 523733e..c44d5b2 100644 --- a/src/QuickSearchInput.tsx +++ b/src/QuickSearchInput.tsx @@ -37,7 +37,7 @@ export interface StatusProps { } const StatusComponent: FC = ({ hasMatch, children, label }) => ( - {`${label || 'Query'}: `}{children} + {`${label || 'Query'}: `}{children} ); export interface Item { From 076943a3a69d80e04fc31864125800194dbf44b7 Mon Sep 17 00:00:00 2001 From: John O'Sullivan Date: Wed, 11 Dec 2019 13:01:15 -0500 Subject: [PATCH 10/10] Upgraded to 1.0.0, published, added a demo repl.it --- README.md | 5 ++--- package-lock.json | 9 +++------ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 88b3b36..4e771d4 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ # ink-quicksearch-input -> QuickSearch Component for [Ink 2](https://github.com/vadimdemedes/ink) - -Forked from [@aicioara](https://github.com/aicioara)'s original [`ink-quicksearch`](https://github.com/aicioara/ink-quicksearch) component to upgrade it to Ink 2. Big thanks to him for laying out the original logic in v1! If you are looking for a component that works with Ink v1, that's where to go. This re-write uses modern React (e.g. function components and hooks), and it is also in Typescript, improving the developer experience. The only behavioral difference is that this component always filters out items which do not match the query. +> QuickSearch Component for [Ink 2](https://github.com/vadimdemedes/ink), [demo on repl.it](https://repl.it/@johnosullivan1/ink-quicksearch-input) +Forked from [@aicioara](https://github.com/aicioara)'s original [`ink-quicksearch`](https://github.com/aicioara/ink-quicksearch) component to upgrade it to Ink 2. Big thanks to him for laying out the original logic in v1! If you are looking for a component that works with Ink v1, that's where to go. This re-write uses modern React (e.g. function components and hooks), and it is also in Typescript, improving the developer experience. The only behavioral difference is that this component always filters out items which do not match the query. Note that the demo runs a good bit slower than it does in an actual terminal; there's some uncanny-valley lag which is not present during normal use. ## Install diff --git a/package-lock.json b/package-lock.json index 98bb6c8..9c206a4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "ink-quicksearch-input", - "version": "0.3.2", + "version": "1.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -13,8 +13,7 @@ "@types/has-ansi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/has-ansi/-/has-ansi-3.0.0.tgz", - "integrity": "sha512-H3vFOwfLlFEC0MOOrcSkus8PCnMCzz4N0EqUbdJZCdDhBTfkAu86aRYA+MTxjKW6jCpUvxcn4715US8g+28BMA==", - "dev": true + "integrity": "sha512-H3vFOwfLlFEC0MOOrcSkus8PCnMCzz4N0EqUbdJZCdDhBTfkAu86aRYA+MTxjKW6jCpUvxcn4715US8g+28BMA==" }, "@types/ink": { "version": "2.0.3", @@ -28,14 +27,12 @@ "@types/lodash": { "version": "4.14.149", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.149.tgz", - "integrity": "sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ==", - "dev": true + "integrity": "sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ==" }, "@types/lodash.isequal": { "version": "4.5.5", "resolved": "https://registry.npmjs.org/@types/lodash.isequal/-/lodash.isequal-4.5.5.tgz", "integrity": "sha512-4IKbinG7MGP131wRfceK6W4E/Qt3qssEFLF30LnJbjYiSfHGGRU/Io8YxXrZX109ir+iDETC8hw8QsDijukUVg==", - "dev": true, "requires": { "@types/lodash": "*" }