UNPKG

hypertoxin

Version:

A themeable ReactNative component library for developing native apps

1,455 lines (1,388 loc) 78.5 kB
/** * 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`