hypertoxin
Version:
A themeable ReactNative component library for developing native apps
1,455 lines (1,388 loc) • 78.5 kB
JavaScript
/**
* Copyright 2016-present Tuan Le.
*
* Licensed under the MIT License.
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://opensource.org/licenses/mit-license.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*------------------------------------------------------------------------
*
* @module SearchField
* @description - Search field input component.
*
* @author Tuan Le (tuan.t.lei@gmail.com)
*
* @flow
*/
'use strict'; // eslint-disable-line
import React from 'react';
import ReactNative from 'react-native'; // eslint-disable-line
import PropTypes from 'prop-types';
import { View as AnimatedView } from 'react-native-animatable';
import dismissKeyboard from 'react-native/Libraries/Utilities/dismissKeyboard';
import {
DefaultTheme,
DefaultThemeContext
} from '../../themes/default-theme';
const {
FlatList
} = ReactNative;
const {
Dimensions,
TextInput
} = ReactNative;
const DEVICE_WIDTH = Dimensions.get(`window`).width;
const DEVICE_HEIGHT = Dimensions.get(`window`).height;
const DEFAULT_ANIMATION_DURATION_MS = 300;
const DEFAULT_SUGGESTION_HISTORY_ITEM_COUNT = 6;
const DEFAULT_DROP_SHADOW_STYLE = {
shadowColor: `#000000`,
shadowRadius: 1,
shadowOpacity: 0.25,
shadowOffset: {
width: 0,
height: 1
}
};
const DEFAULT_SEARCH_FIELD_STYLE = {
small: {
container: {
flexShrink: 1,
flexDirection: `column`,
alignSelf: `stretch`,
alignItems: `stretch`,
justifyContent: `center`,
width: `100%`,
maxWidth: DEVICE_WIDTH,
zIndex: 10,
backgroundColor: `transparent`,
overflow: `hidden`
},
box: {
focused: {
flexDirection: `row`,
alignItems: `center`,
justifyContent: `space-between`,
height: DefaultTheme.field.size.search.input.small + 6,
paddingVertical: 3,
paddingHorizontal: 3
},
blurred: {
flexDirection: `row`,
alignItems: `center`,
justifyContent: `space-between`,
height: DefaultTheme.field.size.search.input.small + 6,
paddingVertical: 3,
paddingHorizontal: 3
}
},
contentLeftRoom: {
flexDirection: `row`,
alignItems: `center`,
justifyContent: `center`,
backgroundColor: `transparent`
},
contentRightRoom: {
flexDirection: `row`,
alignItems: `center`,
justifyContent: `center`,
backgroundColor: `transparent`
},
input: {
...DefaultTheme.field.font.search.input.small,
flexGrow: 1,
textAlign: `left`,
height: DefaultTheme.field.size.search.input.small,
backgroundColor: `transparent`
},
suggestion: {
position: `absolute`,
flexDirection: `column`,
alignItems: `stretch`,
justifyContent: `center`,
width: DEVICE_WIDTH,
height: DEVICE_HEIGHT,
zIndex: 10,
transform: [{
translateY: DEVICE_HEIGHT
}]
}
},
normal: {
container: {
flexShrink: 1,
flexDirection: `column`,
alignSelf: `stretch`,
alignItems: `stretch`,
justifyContent: `center`,
width: `100%`,
maxWidth: DEVICE_WIDTH,
zIndex: 10,
backgroundColor: `transparent`,
overflow: `hidden`
},
box: {
focused: {
flexDirection: `row`,
alignItems: `center`,
justifyContent: `space-between`,
height: DefaultTheme.field.size.search.input.normal + 6,
paddingVertical: 3,
paddingHorizontal: 3
},
blurred: {
flexDirection: `row`,
alignItems: `center`,
justifyContent: `space-between`,
height: DefaultTheme.field.size.search.input.normal + 6,
paddingVertical: 3,
paddingHorizontal: 3
}
},
contentLeftRoom: {
flexDirection: `row`,
alignItems: `center`,
justifyContent: `center`,
backgroundColor: `transparent`
},
contentRightRoom: {
flexDirection: `row`,
alignItems: `center`,
justifyContent: `center`,
backgroundColor: `transparent`
},
input: {
...DefaultTheme.field.font.search.input.normal,
flexGrow: 1,
textAlign: `left`,
height: DefaultTheme.field.size.search.input.normal,
backgroundColor: `transparent`
},
suggestion: {
position: `absolute`,
flexDirection: `column`,
alignItems: `stretch`,
justifyContent: `center`,
width: DEVICE_WIDTH,
height: DEVICE_HEIGHT,
zIndex: 10,
transform: [{
translateY: DEVICE_HEIGHT
}]
}
},
large: {
container: {
flexShrink: 1,
flexDirection: `column`,
alignSelf: `stretch`,
alignItems: `stretch`,
justifyContent: `center`,
width: `100%`,
maxWidth: DEVICE_WIDTH,
zIndex: 10,
backgroundColor: `transparent`,
overflow: `hidden`
},
box: {
focused: {
flexDirection: `row`,
alignItems: `center`,
justifyContent: `space-between`,
height: DefaultTheme.field.size.search.input.large + 6,
paddingVertical: 3,
paddingHorizontal: 3
},
blurred: {
flexDirection: `row`,
alignItems: `center`,
justifyContent: `space-between`,
height: DefaultTheme.field.size.search.input.large + 6,
paddingVertical: 3,
paddingHorizontal: 3
}
},
contentLeftRoom: {
flexDirection: `row`,
alignItems: `center`,
justifyContent: `center`,
backgroundColor: `transparent`
},
contentRightRoom: {
flexDirection: `row`,
alignItems: `center`,
justifyContent: `center`,
backgroundColor: `transparent`
},
input: {
...DefaultTheme.field.font.search.input.large,
flexGrow: 1,
textAlign: `left`,
height: DefaultTheme.field.size.search.input.large,
backgroundColor: `transparent`
},
suggestion: {
position: `absolute`,
flexDirection: `column`,
alignItems: `stretch`,
justifyContent: `center`,
width: DEVICE_WIDTH,
height: DEVICE_HEIGHT,
zIndex: 10,
transform: [{
translateY: DEVICE_HEIGHT
}]
}
}
};
const isEmptyInputValue = (value) => {
if (typeof value === `object`) {
return Object.getOwnPropertyNames(value).length === 0;
} else if (Array.isArray(value) || typeof value === `string`) {
return value.length === 0;
}
return true;
};
const readjustStyle = (newStyle = {
shade: `themed`,
overlay: `themed`,
corner: `themed`,
margin: `themed`,
dropShadowed: `themed`
}, prevAdjustedStyle = DEFAULT_SEARCH_FIELD_STYLE, Theme = DefaultTheme) => {
const {
shade,
overlay,
corner,
margin,
dropShadowed,
style
} = newStyle;
const themedShade = shade === `themed` ? Theme.field.search.shade : shade;
const themedOverlay = overlay === `themed` ? Theme.field.search.overlay : overlay;
const themedSuggestionColor = Theme.field.color.search.box[themedShade];
const nullMargin = {
margin: null,
marginTop: null,
marginBottom: null,
marginLeft: null,
marginRight: null,
marginHorizontal: null,
marginVertical: null
};
const nullBorderRadius = {
borderTopLeftRadius: null,
borderTopRightRadius: null,
borderBottomLeftRadius: null,
borderBottomRightRadius: null
};
let themedColor;
let themedBorderRadius;
let themedBoxBorderWidth = 0;
let themedBoxBorderFocusColor;
let themedBoxBorderBlurColor;
let themedInputColor;
let themedFocusColor;
let themedBlurColor;
let dropShadow;
let themedCorner;
let themedMargin;
themedFocusColor = Theme.field.color.search.focused[themedShade];
themedBlurColor = Theme.field.color.search.blurred[themedShade];
switch (themedOverlay) { // eslint-disable-line
case `opaque`:
themedColor = Theme.field.color.search.box[themedShade];
themedBoxBorderFocusColor = `transparent`;
themedBoxBorderBlurColor = `transparent`;
break;
case `translucent`:
themedColor = `${Theme.field.color.search.box[themedShade]}${Theme.field.color.search.opacity}`;
themedBoxBorderFocusColor = `transparent`;
themedBoxBorderBlurColor = `transparent`;
break;
case `translucent-outline`:
themedColor = `${Theme.field.color.search.box[themedShade]}${Theme.field.color.search.opacity}`;
themedBoxBorderWidth = 1;
themedBoxBorderFocusColor = themedFocusColor;
themedBoxBorderBlurColor = themedBlurColor;
break;
case `transparent`:
themedColor = `transparent`;
themedBoxBorderFocusColor = `transparent`;
themedBoxBorderBlurColor = `transparent`;
break;
case `transparent-outline`:
themedColor = `transparent`;
themedBoxBorderWidth = 1;
themedBoxBorderFocusColor = themedFocusColor;
themedBoxBorderBlurColor = themedBlurColor;
break;
}
if (typeof corner === `string`) {
if (corner === `themed`) {
if (typeof Theme.field.search.corner === `string` && Theme.field.corner.search.hasOwnProperty(Theme.field.search.corner)) {
themedCorner = Theme.field.corner.search[Theme.field.search.corner];
} else {
themedCorner = Theme.field.search.corner;
}
} else if (Theme.field.corner.search.hasOwnProperty(corner)) {
themedCorner = Theme.field.corner.search[corner];
} else {
themedCorner = 0;
}
} else {
themedCorner = corner;
}
if (typeof themedCorner === `number`) {
themedBorderRadius = {
small: {
borderRadius: themedCorner
},
normal: {
borderRadius: themedCorner
},
large: {
borderRadius: themedCorner
}
};
} else if (typeof themedCorner === `object`) {
if (themedCorner.hasOwnProperty(`small`) && typeof themedCorner.small === `object` &&
themedCorner.hasOwnProperty(`normal`) && typeof themedCorner.normal === `object` &&
themedCorner.hasOwnProperty(`large`) && typeof themedCorner.large === `object`) {
themedBorderRadius = {
small: Object.entries(themedCorner.small).reduce((_themedBorderRadius, [ key, value ]) => {
let _borderRadius = nullBorderRadius;
_borderRadius[`border${key.charAt(0).toUpperCase()}${key.slice(1)}Radius`] = value;
return {
..._themedBorderRadius,
..._borderRadius
};
}, nullBorderRadius),
normal: Object.entries(themedCorner.normal).reduce((_themedBorderRadius, [ key, value ]) => {
let _borderRadius = nullBorderRadius;
_borderRadius[`border${key.charAt(0).toUpperCase()}${key.slice(1)}Radius`] = value;
return {
..._themedBorderRadius,
..._borderRadius
};
}, nullBorderRadius),
large: Object.entries(themedCorner.large).reduce((_themedBorderRadius, [ key, value ]) => {
let _borderRadius = nullBorderRadius;
_borderRadius[`border${key.charAt(0).toUpperCase()}${key.slice(1)}Radius`] = value;
return {
..._themedBorderRadius,
..._borderRadius
};
}, nullBorderRadius)
};
} else if (themedCorner.hasOwnProperty(`small`) && typeof themedCorner.small === `number` &&
themedCorner.hasOwnProperty(`normal`) && typeof themedCorner.normal === `number` &&
themedCorner.hasOwnProperty(`large`) && typeof themedCorner.large === `number`) {
themedBorderRadius = {
small: {
borderRadius: themedCorner.small
},
normal: {
borderRadius: themedCorner.normal
},
large: {
borderRadius: themedCorner.large
}
};
} else {
themedBorderRadius = {
small: {
...nullBorderRadius,
...themedCorner
},
normal: {
...nullBorderRadius,
...themedCorner
},
large: {
...nullBorderRadius,
...themedCorner
}
};
}
}
if (typeof margin === `string`) {
if (margin === `themed`) {
if (typeof Theme.field.search.margin === `string` && Theme.field.margin.search.hasOwnProperty(Theme.field.search.margin)) {
themedMargin = Theme.field.margin.search[Theme.field.search.margin];
} else {
themedMargin = Theme.field.search.margin;
}
} else if (Theme.field.margin.search.hasOwnProperty(margin)) {
themedMargin = Theme.field.margin.search[margin];
} else {
themedMargin = 0;
}
} else {
themedMargin = margin;
}
if (typeof themedMargin === `number`) {
themedMargin = {
...nullMargin,
margin: themedMargin
};
} else if (typeof themedMargin === `object`) {
themedMargin = Object.entries(themedMargin).reduce((_themedMargin, [ key, value ]) => {
let _margin = nullMargin;
_margin[`margin${key.charAt(0).toUpperCase()}${key.slice(1)}`] = value;
_themedMargin = {
..._themedMargin,
..._margin
};
return _themedMargin;
}, nullMargin);
}
themedInputColor = Theme.field.color.search.input[themedShade];
if ((typeof dropShadowed === `boolean` && dropShadowed) || (dropShadowed === `themed` && Theme.field.search.dropShadowed)) {
dropShadow = DEFAULT_DROP_SHADOW_STYLE;
} else {
dropShadow = {
...DEFAULT_DROP_SHADOW_STYLE,
shadowRadius: 0,
shadowOpacity: 0,
shadowOffset: {
width: 0,
height: 0
}
};
}
return {
small: {
container: {
...prevAdjustedStyle.small.container,
...themedMargin,
...(typeof style === `object` && style.hasOwnProperty(`container`) && typeof style.container === `object` ? style.container : {})
},
box: {
focused: {
...prevAdjustedStyle.small.box.focused,
...dropShadow,
...themedBorderRadius.small,
height: Theme.field.size.search.input.small + 6,
borderWidth: themedBoxBorderWidth,
borderColor: themedBoxBorderFocusColor,
backgroundColor: themedColor,
...(typeof style === `object` && style.hasOwnProperty(`box`) &&
typeof style.box === `object` && style.box.hasOwnProperty(`focused`) && typeof style.box.focused === `object` ? style.box.focused : {})
},
blurred: {
...prevAdjustedStyle.small.box.blurred,
...dropShadow,
...themedBorderRadius.small,
height: Theme.field.size.search.input.small + 6,
borderWidth: themedBoxBorderWidth,
borderColor: themedBoxBorderBlurColor,
backgroundColor: themedColor,
...(typeof style === `object` && style.hasOwnProperty(`box`) &&
typeof style.box === `object` && style.box.hasOwnProperty(`blurred`) && typeof style.box.blurred === `object` ? style.box.blurred : {})
}
},
contentLeftRoom: {
...prevAdjustedStyle.small.contentLeftRoom,
...(typeof style === `object` && style.hasOwnProperty(`contentLeftRoom`) && typeof style.contentLeftRoom ? style.contentLeftRoom : {})
},
contentRightRoom: {
...prevAdjustedStyle.small.contentRightRoom,
...(typeof style === `object` && style.hasOwnProperty(`contentRightRoom`) && typeof style.contentRightRoom ? style.contentRightRoom : {})
},
input: {
...prevAdjustedStyle.small.input,
...Theme.field.font.search.input.small,
height: Theme.field.size.search.input.small,
color: themedInputColor,
...(typeof style === `object` && style.hasOwnProperty(`input`) && typeof style.input === `object` ? style.input : {})
},
suggestion: {
...prevAdjustedStyle.small.suggestion,
...dropShadow,
backgroundColor: themedSuggestionColor,
...(typeof style === `object` && style.hasOwnProperty(`suggestion`) && typeof style.suggestion === `object` ? style.suggestion : {})
}
},
normal: {
container: {
...prevAdjustedStyle.normal.container,
...themedMargin,
...(typeof style === `object` && style.hasOwnProperty(`container`) && typeof style.container === `object` ? style.container : {})
},
box: {
focused: {
...prevAdjustedStyle.normal.box.focused,
...dropShadow,
...themedBorderRadius.normal,
height: Theme.field.size.search.input.normal + 6,
borderWidth: themedBoxBorderWidth,
borderColor: themedBoxBorderFocusColor,
backgroundColor: themedColor,
...(typeof style === `object` && style.hasOwnProperty(`box`) &&
typeof style.box === `object` && style.box.hasOwnProperty(`focused`) && typeof style.box.focused === `object` ? style.box.focused : {})
},
blurred: {
...prevAdjustedStyle.normal.box.blurred,
...dropShadow,
...themedBorderRadius.normal,
height: Theme.field.size.search.input.normal + 6,
borderWidth: themedBoxBorderWidth,
borderColor: themedBoxBorderBlurColor,
backgroundColor: themedColor,
...(typeof style === `object` && style.hasOwnProperty(`box`) &&
typeof style.box === `object` && style.box.hasOwnProperty(`blurred`) && typeof style.box.blurred === `object` ? style.box.blurred : {})
}
},
contentLeftRoom: {
...prevAdjustedStyle.normal.contentLeftRoom,
...(typeof style === `object` && style.hasOwnProperty(`contentLeftRoom`) && typeof style.contentLeftRoom ? style.contentLeftRoom : {})
},
contentRightRoom: {
...prevAdjustedStyle.normal.contentRightRoom,
...(typeof style === `object` && style.hasOwnProperty(`contentRightRoom`) && typeof style.contentRightRoom ? style.contentRightRoom : {})
},
input: {
...prevAdjustedStyle.normal.input,
...Theme.field.font.search.input.normal,
height: Theme.field.size.search.input.normal,
color: themedInputColor,
...(typeof style === `object` && style.hasOwnProperty(`input`) && typeof style.input === `object` ? style.input : {})
},
suggestion: {
...prevAdjustedStyle.normal.suggestion,
...dropShadow,
backgroundColor: themedSuggestionColor,
...(typeof style === `object` && style.hasOwnProperty(`suggestion`) && typeof style.suggestion === `object` ? style.suggestion : {})
}
},
large: {
container: {
...prevAdjustedStyle.large.container,
...themedMargin,
...(typeof style === `object` && style.hasOwnProperty(`container`) && typeof style.container === `object` ? style.container : {})
},
box: {
focused: {
...prevAdjustedStyle.large.box.focused,
...dropShadow,
...themedBorderRadius.large,
height: Theme.field.size.search.input.large + 6,
borderWidth: themedBoxBorderWidth,
borderColor: themedBoxBorderFocusColor,
backgroundColor: themedColor,
...(typeof style === `object` && style.hasOwnProperty(`box`) &&
typeof style.box === `object` && style.box.hasOwnProperty(`focused`) && typeof style.box.focused === `object` ? style.box.focused : {})
},
blurred: {
...prevAdjustedStyle.large.box.blurred,
...dropShadow,
...themedBorderRadius.large,
height: Theme.field.size.search.input.large + 6,
borderWidth: themedBoxBorderWidth,
borderColor: themedBoxBorderBlurColor,
backgroundColor: themedColor,
...(typeof style === `object` && style.hasOwnProperty(`box`) &&
typeof style.box === `object` && style.box.hasOwnProperty(`blurred`) && typeof style.box.blurred === `object` ? style.box.blurred : {})
}
},
contentLeftRoom: {
...prevAdjustedStyle.large.contentLeftRoom,
...(typeof style === `object` && style.hasOwnProperty(`contentLeftRoom`) && typeof style.contentLeftRoom ? style.contentLeftRoom : {})
},
contentRightRoom: {
...prevAdjustedStyle.large.contentRightRoom,
...(typeof style === `object` && style.hasOwnProperty(`contentRightRoom`) && typeof style.contentRightRoom ? style.contentRightRoom : {})
},
input: {
...prevAdjustedStyle.large.input,
...Theme.field.font.search.input.large,
height: Theme.field.size.search.input.large,
color: themedInputColor,
...(typeof style === `object` && style.hasOwnProperty(`input`) && typeof style.input === `object` ? style.input : {})
},
suggestion: {
...prevAdjustedStyle.large.suggestion,
...dropShadow,
backgroundColor: themedSuggestionColor,
...(typeof style === `object` && style.hasOwnProperty(`suggestion`) && typeof style.suggestion === `object` ? style.suggestion : {})
}
}
};
};
async function fetchAutocompletionItems (value, getAutocompletionValues) {
const component = this;
if (!isEmptyInputValue(value)) {
const autocompletionValues = await getAutocompletionValues(value);
if (Array.isArray(autocompletionValues) && autocompletionValues.length > 0) {
component.setState((prevState) => {
return {
suggestion: {
...prevState.suggestion,
autocompleteItems: [
...new Set(autocompletionValues.filter((autocompleteText) => typeof autocompleteText === `string`))
].map((autocompleteText) => {
return {
suggestionType: `autocompletion`,
value: autocompleteText
};
})
}
};
});
} else {
component.setState((prevState) => {
return {
suggestion: {
...prevState.suggestion,
autocompleteItems: []
}
};
});
}
} else {
component.setState((prevState) => {
return {
suggestion: {
...prevState.suggestion,
autocompleteItems: []
}
};
});
}
}
export default class SearchField extends React.Component {
static contextType = DefaultThemeContext
static propTypes = {
exclusions: PropTypes.arrayOf(PropTypes.string),
room: PropTypes.oneOf([
`none`,
`content-left`, `content-middle`, `content-right`,
`content-bottom`, `content-top`,
`media`
]),
shade: PropTypes.oneOf([ `themed`, `light`, `dark` ]),
overlay: PropTypes.oneOf([ `themed`, `opaque`, `translucent`, `translucent-outline`, `transparent`, `transparent-outline` ]),
corner: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.shape({
topLeft: PropTypes.number,
topRight: PropTypes.number,
bottomLeft: PropTypes.number,
bottomRight: PropTypes.number
})
]),
size: PropTypes.oneOf([ `themed`, `small`, `normal`, `large` ]),
margin: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.shape({
top: PropTypes.number,
bottom: PropTypes.number,
left: PropTypes.number,
right: PropTypes.number,
horizontal: PropTypes.number,
vertical: PropTypes.number
})
]),
dropShadowed: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.string
]),
autoFocus: PropTypes.bool,
autoCorrect: PropTypes.bool,
suggestive: PropTypes.bool,
pinnedSuggestionValues: PropTypes.arrayOf(PropTypes.oneOfType([ PropTypes.number, PropTypes.string, PropTypes.object ])),
hint: PropTypes.string,
initialAnimation: PropTypes.oneOfType([
PropTypes.string,
PropTypes.shape({
refName: PropTypes.string,
transitions: PropTypes.arrayOf(PropTypes.shape({
to: PropTypes.oneOfType([
PropTypes.func,
PropTypes.object
]),
from: PropTypes.oneOfType([
PropTypes.func,
PropTypes.object
]),
option: PropTypes.shape({
duration: PropTypes.number,
delay: PropTypes.number,
easing: PropTypes.string
})
})),
onTransitionBegin: PropTypes.func,
onTransitionEnd: PropTypes.func,
onAnimationBegin: PropTypes.func,
onAnimationEnd: PropTypes.func
})
]),
onSearch: PropTypes.func,
onGetAutocompletionValues: PropTypes.func,
onFocus: PropTypes.func,
onBlur: PropTypes.func,
onCollapse: PropTypes.func,
onExpand: PropTypes.func,
onHide: PropTypes.func,
onShow: PropTypes.func,
onHideSuggestion: PropTypes.func,
onShowSuggestion: PropTypes.func,
onClear: PropTypes.func,
onClearSuggestion: PropTypes.func,
renderSuggestionItem: PropTypes.func
}
static defaultProps = {
exclusions: [ `` ],
room: `none`,
shade: `themed`,
overlay: `themed`,
corner: `themed`,
size: `themed`,
margin: `themed`,
dropShadowed: `themed`,
autoFocus: false,
autoCorrect: true,
suggestive: true,
pinnedSuggestionValues: [],
hint: ``,
initialAnimation: `themed`,
onSearch: () => null,
onGetAutocompletionValues: () => null,
onFocus: () => null,
onBlur: () => null,
onCollapse: () => null,
onExpand: () => null,
onHide: () => null,
onShow: () => null,
onHideSuggestion: () => null,
onShowSuggestion: () => null,
onClear: () => null,
onClearSuggestion: () => null,
renderSuggestionItem: () => null // eslint-disable-line
}
static getDerivedStateFromProps (props, state) {
const {
shade,
overlay,
corner,
margin,
dropShadowed,
style
} = props;
const {
Theme
} = state.context;
return {
adjustedStyle: readjustStyle({
shade,
overlay,
corner,
margin,
dropShadowed,
style
}, state.adjustedStyle, Theme)
};
}
constructor (props) {
super(props);
const component = this;
const themedSize = props.size === `themed` ? DefaultTheme.field.search.size : props.size;
component.refCache = {};
component.state = {
context: {
Theme: DefaultTheme
},
visibility: {
box: true,
suggestion: false
},
collapsed: false,
adjustedStyle: DEFAULT_SEARCH_FIELD_STYLE,
container: {
width: 0,
height: DefaultTheme.field.size.search.input[themedSize] + 6,
top: 0,
left: 0
},
input: {
focused: false,
value: ``
},
suggestion: {
historyItems: [],
autocompleteItems: []
}
};
}
isCollapsed = () => {
const component = this;
const {
collapsed
} = component.state;
return collapsed;
}
isVisible = () => {
const component = this;
const {
visibility
} = component.state;
return visibility.box;
}
isSuggestionVisible = () => {
const component = this;
const {
visibility
} = component.state;
return visibility.suggestion;
}
isFocused = () => {
const component = this;
const textInput = component.refCache[`text-input`];
if (textInput !== undefined) {
return textInput.isFocused();
}
return false;
}
collapse = (animation = {
transitions: null,
onTransitionBegin: () => null,
onTransitionEnd: () => null,
onAnimationBegin: () => null,
onAnimationEnd: () => null
}) => {
const component = this;
const {
size,
onClear,
onCollapse
} = component.props;
const {
visibility,
collapsed
} = component.state;
const {
Theme
} = component.context;
const themedSize = size === `themed` ? Theme.field.search.size : size;
if (typeof animation === `object`) {
const {
transitions,
onTransitionBegin,
onTransitionEnd,
onAnimationBegin,
onAnimationEnd
} = animation;
let animationPromises = [];
if (!collapsed) {
animationPromises = component.animate({
refName: `animated-box-view`,
transitions: Array.isArray(transitions) ? transitions : [{
to: {
width: Theme.field.size.search.input[themedSize] + 6
},
option: {
duration: DEFAULT_ANIMATION_DURATION_MS,
delay: 0,
easing: `linear`
}
}],
onTransitionBegin,
onTransitionEnd,
onAnimationBegin,
onAnimationEnd
});
if (visibility.suggestion) {
component.animate({
refName: `animated-suggestion-view`,
transitions: [{
to: {
opacity: 0,
translateY: DEVICE_HEIGHT
},
option: {
duration: DEFAULT_ANIMATION_DURATION_MS,
delay: 0,
easing: `linear`
}
}]
});
}
component.setState((prevState) => {
return {
collapsed: true,
visibility: {
...prevState.visibility,
suggestion: false
},
input: {
...prevState.input,
value: ``
},
suggestion: {
...prevState.suggestion,
autocompleteItems: []
}
};
}, () => {
component.blur();
onCollapse();
onClear();
});
return animationPromises;
}
}
}
expand = (animation = {
transitions: null,
onTransitionBegin: () => null,
onTransitionEnd: () => null,
onAnimationBegin: () => null,
onAnimationEnd: () => null
}) => {
const component = this;
const {
onExpand
} = component.props;
const {
collapsed,
container
} = component.state;
if (typeof animation === `object`) {
const {
transitions,
onTransitionBegin,
onTransitionEnd,
onAnimationBegin,
onAnimationEnd
} = animation;
let animationPromises = [];
if (collapsed) {
animationPromises = component.animate({
refName: `animated-box-view`,
transitions: Array.isArray(transitions) ? transitions : [{
to: {
width: container.width
},
option: {
duration: DEFAULT_ANIMATION_DURATION_MS,
delay: 0,
easing: `linear`
}
}],
onTransitionBegin,
onTransitionEnd,
onAnimationBegin,
onAnimationEnd
});
component.setState(() => {
return {
collapsed: false
};
}, () => {
onExpand();
});
return animationPromises;
}
}
}
show = (animation = {
transitions: null,
onTransitionBegin: () => null,
onTransitionEnd: () => null,
onAnimationBegin: () => null,
onAnimationEnd: () => null
}) => {
const component = this;
const {
onShow
} = component.props;
const {
visibility
} = component.state;
if (typeof animation === `object`) {
const {
transitions,
onTransitionBegin,
onTransitionEnd,
onAnimationBegin,
onAnimationEnd
} = animation;
let animationPromises = [];
if (!visibility.box) {
animationPromises = component.animate({
refName: `animated-box-view`,
transitions: Array.isArray(transitions) ? transitions : [{
to: {
opacity: 1
},
option: {
duration: DEFAULT_ANIMATION_DURATION_MS,
delay: 0,
easing: `linear`
}
}],
onTransitionBegin,
onTransitionEnd,
onAnimationBegin,
onAnimationEnd
});
component.setState((prevState) => {
return {
visibility: {
...prevState.visibility,
box: true
}
};
}, () => {
onShow();
});
return animationPromises;
}
}
}
showSuggestion = (animation = {
transitions: null,
onTransitionBegin: () => null,
onTransitionEnd: () => null,
onAnimationBegin: () => null,
onAnimationEnd: () => null
}) => {
const component = this;
const {
onShowSuggestion
} = component.props;
const {
visibility
} = component.state;
if (typeof animation === `object`) {
const {
transitions,
onTransitionBegin,
onTransitionEnd,
onAnimationBegin,
onAnimationEnd
} = animation;
let animationPromises = [];
if (visibility.box && !visibility.suggestion) {
animationPromises = component.animate({
refName: `animated-suggestion-view`,
transitions: Array.isArray(transitions) ? transitions : [{
to: {
opacity: 1,
translateY: 0
},
option: {
duration: DEFAULT_ANIMATION_DURATION_MS,
delay: 0,
easing: `linear`
}
}],
onTransitionBegin,
onTransitionEnd,
onAnimationBegin,
onAnimationEnd
});
}
component.setState((prevState) => {
return {
visibility: {
...prevState.visibility,
suggestion: true
}
};
}, () => {
onShowSuggestion();
});
return animationPromises;
}
}
hideSuggestion = (animation = {
transitions: null,
onTransitionBegin: () => null,
onTransitionEnd: () => null,
onAnimationBegin: () => null,
onAnimationEnd: () => null
}) => {
const component = this;
const {
onHideSuggestion
} = component.props;
const {
visibility
} = component.state;
if (typeof animation === `object`) {
const {
transitions,
onTransitionBegin,
onTransitionEnd,
onAnimationBegin,
onAnimationEnd
} = animation;
let animationPromises = [];
if (visibility.box && visibility.suggestion) {
animationPromises = component.animate({
refName: `animated-suggestion-view`,
transitions: Array.isArray(transitions) ? transitions : [{
to: {
opacity: 0,
translateY: DEVICE_HEIGHT
},
option: {
duration: DEFAULT_ANIMATION_DURATION_MS,
delay: 0,
easing: `linear`
}
}],
onTransitionBegin,
onTransitionEnd,
onAnimationBegin,
onAnimationEnd
});
}
component.setState((prevState) => {
return {
visibility: {
...prevState.visibility,
suggestion: false
}
};
}, () => {
component.blur();
onHideSuggestion();
});
return animationPromises;
}
}
hide = (animation = {
transitions: null,
onTransitionBegin: () => null,
onTransitionEnd: () => null,
onAnimationBegin: () => null,
onAnimationEnd: () => null
}) => {
const component = this;
const {
onHide
} = component.props;
const {
visibility
} = component.state;
if (typeof animation === `object`) {
const {
transitions,
onTransitionBegin,
onTransitionEnd,
onAnimationBegin,
onAnimationEnd
} = animation;
let animationPromises = [];
if (visibility.box) {
animationPromises = component.animate({
refName: `animated-box-view`,
transitions: Array.isArray(transitions) ? transitions : [{
to: {
opacity: 0
},
option: {
duration: DEFAULT_ANIMATION_DURATION_MS,
delay: 0,
easing: `linear`
}
}],
onTransitionBegin,
onTransitionEnd,
onAnimationBegin,
onAnimationEnd
});
if (visibility.suggestion) {
component.animate({
refName: `animated-suggestion-view`,
transitions: [{
to: {
opacity: 0,
translateY: DEVICE_HEIGHT
},
option: {
duration: DEFAULT_ANIMATION_DURATION_MS,
delay: 0,
easing: `linear`
}
}]
});
}
component.setState((prevState) => {
return {
visibility: {
...prevState.visibility,
box: false,
suggestion: false
}
};
}, () => {
component.blur();
onHide();
});
return animationPromises;
}
}
}
focus = () => {
const component = this;
const textInput = component.refCache[`text-input`];
if (textInput !== undefined && !textInput.isFocused()) {
textInput.focus();
}
}
blur = () => {
const component = this;
const textInput = component.refCache[`text-input`];
if (textInput !== undefined && textInput.isFocused()) {
textInput.blur();
}
}
clear = () => {
const component = this;
const {
onClear
} = component.props;
component.setState((prevState) => {
return {
input: {
...prevState.input,
value: ``
},
suggestion: {
...prevState.suggestion,
autocompleteItems: []
}
};
}, () => {
onClear();
});
}
clearSuggestion = () => {
const component = this;
const {
onClearSuggestion
} = component.props;
component.setState(() => {
return {
suggestion: {
historyItems: [],
autocompleteItems: []
}
};
}, () => {
onClearSuggestion();
});
}
onLayout = (event) => {
const component = this;
const {
width,
height,
x: left,
y: top
} = event.nativeEvent.layout;
component.setState(() => {
return {
container: {
width,
height,
top,
left
}
};
});
}
onFocus = () => {
const component = this;
const {
suggestive,
onFocus,
onShowSuggestion
} = component.props;
const {
visibility,
collapsed
} = component.state;
if (suggestive && !collapsed && visibility.box && !visibility.suggestion) {
component.animate({
refName: `animated-suggestion-view`,
transitions: [{
to: {
opacity: 1,
translateY: 0
},
option: {
duration: DEFAULT_ANIMATION_DURATION_MS,
delay: 0,
easing: `linear`
}
}]
});
component.setState((prevState) => {
return {
visibility: {
...prevState.visibility,
suggestion: true
},
input: {
...prevState.input,
focused: true
}
};
}, () => {
onShowSuggestion();
});
} else {
component.setState((prevState) => {
return {
input: {
...prevState.input,
focused: true
}
};
}, () => {
onFocus();
});
}
}
onBlur = () => {
const component = this;
const {
suggestive,
pinnedSuggestionValues,
onBlur,
onHideSuggestion
} = component.props;
const {
visibility,
collapsed,
input,
suggestion
} = component.state;
if (suggestive && !collapsed && visibility.box && visibility.suggestion &&
isEmptyInputValue(input.value) && pinnedSuggestionValues.length === 0 && suggestion.historyItems.length === 0) {
component.animate({
refName: `animated-suggestion-view`,
transitions: [{
to: {
opacity: 0,
translateY: DEVICE_HEIGHT
},
option: {
duration: DEFAULT_ANIMATION_DURATION_MS,
delay: 0,
easing: `linear`