UNPKG

@fto-consult/expo-ui

Version:

Bibliothèque de composants UI Expo,react-native

1,124 lines (1,104 loc) 63.3 kB
/***** si la props items est une fonction, alors elle ne doit en aucun cas retourner une promesse, mais surtout un tableau un un objet */ import PropTypes from "prop-types"; import View from "$ecomponents/View"; import {Dimensions,Pressable,StyleSheet,Animated,} from "react-native"; import {TouchableRipple as RNTouchableRiple} from "react-native-paper"; import Divider from "$ecomponents/Divider"; import React, {Fragment,Component as AppComponent} from "$react"; import theme,{Colors} from "$theme"; import Dialog from "$ecomponents/Dialog"; import {isMobileOrTabletMedia} from "$cplatform/dimensions"; import {isIos} from "$cplatform"; import {defaultVal,defaultStr,defaultObj,defaultBool,defaultFunc,debounce,isNonNullString,compare as NCompare,uniqid} from "$cutils"; import MenuComponent from "$ecomponents/Menu"; import HelperText from "$ecomponents/HelperText"; import TextField,{flatMode} from "$ecomponents/TextField"; import List,{MIN_HEIGHT,BigList} from "$ecomponents/List"; import Icon,{ICON_SIZE,ICON_OFFSET,MORE_ICON} from "$ecomponents/Icon"; import Label from "$ecomponents/Label"; import { matchOperators,getSearchTimeout,canAutoFocusSearchField} from "./utils"; import { ProgressBar,ActivityIndicator} from 'react-native-paper'; import Menu from "$ecomponents/Menu/Menu"; import Chip from "$ecomponents/Chip"; import {Content as BottomSheet,Menu as BottomSheetMenu,getContentHeight} from "$ecomponents/BottomSheet"; import {isWeb} from "$cplatform"; import Tooltip from "$ecomponents/Tooltip"; import TouchableRipple from "$ecomponents/TouchableRipple"; import stableHash from "stable-hash"; const _isIos = isIos(); const MAX_SELECTED_ITEMS = 2; export const isValidValueKey = valueKey => isNonNullString(valueKey); /*** au cas où les données sont chargées depuis la bd, la props isLoading doit être passée à true à ce compossant elle devra être remise à false une fois le chargement des données terminé */ class DropdownComponent extends AppComponent { constructor(props){ super(props); const {getValueKey,getItemKey,visible,renderItem,multiple,defaultValue,selected,compare,itemValue,getItemValue} = props; this.keysRefs = []; Object.defineProperties(this,{ fieldsToSort : { value : this.prepareSortableFields(),override : false, writable : false, }, ___hasErrorSymbol : {value:uniqid(`${this.props.name||''}error-symbol-prop`)}, hasNoValidSelectedValue : {value:()=>{ return this.isLoading()? false : !!this[this.___hasErrorSymbol]; }}, toggleHasNoValidSelectedValue : {value:(bool)=>{ if(typeof bool =='boolean'){ this[this.___hasErrorSymbol] = bool; } return this[this.___hasErrorSymbol]; }}, getItemKey : {value : typeof getItemKey =='function' ? getItemKey: (item,index)=>React.key(item,index),override:false,writable:false}, getValueKey : { value : typeof getValueKey =='function'? getValueKey : (value,warn)=>{ const backValue = value; if(typeof value ==='boolean' || typeof value =='string' || typeof value =='number'){ value = value+""; } else { if(warn === true){ console.warn("You must specify key value for value for dropdown component",backValue,props); } if(isObj(value)){ value = JSON.stringify(value); } } return isValidValueKey(value)? value : ""; } }, renderItem : { value : typeof renderItem =='function'? renderItem : ({item,index}) =>{ if(React.isValidElement(item,true) || isDecimal(item) || typeof item =='boolean') return item; if(isObj(item) ) { const itemLabel = this.props.itemLabel; if(isNonNullString(itemLabel)){ let iLabel = ""; itemLabel.trim().split(",").map((itL)=>{ let itV = item.hasOwnProperty(itL) ? item[itL] : undefined; if(typeof formatItemLabel ==="function"){ const itt = formatItemLabel({value:itV,code:itV,columnField:itL,label:itL,item}); if(isNonNullString(itt) || typeof itt =="number"){ itV = String(itt); } } if(itV !== undefined){ iLabel += " "+String(itV); } }); if(iLabel) return iLabel; } if(isNonNullString(item.label)) return item.label; return defaultStr(item.text,item[index],item.code); } return undefined; },override : false, writable : false }, getItemValue : { value : typeof itemValue =='function'? itemValue : typeof getItemValue =='function'? getItemValue : ({item,index}) =>{ if((isObj(item) && item.hasOwnProperty('code'))){ return item.code; } return index; }, override : false, writable : false }, isDefaultMultiplePropEnabled : { value : !!multiple, override : false, writable : false, }, isBigList : { value : true,//multiple || this.props.dynamicContent ? true : false, override : false, }, }); extendObj(this.state,this.prepareItems()); extendObj(this.state,{ initialized : false, selected : this.prepareSelected({defaultValue}), filterText : "", anchorHeight : undefined, isMobileMedia : isMobileOrTabletMedia(), layout : { height: 0, width: 0, }, visible : typeof visible ==='boolean'? visible : false, }) this.anchorRef = React.createRef(null); this.inputRef = React.createRef(null); this.listRef = React.createRef(null); } canHandleMultiple (){ return !!this.props.multiple; } updateSelected (nState,force){ nState = defaultObj(nState); //this.countEee = defaultNumber(this.countEee)+1; if(!("selectedText" in nState)){ nState.selectedText = this.getSelectedText(nState); } const previousSelected = this.state.selected; const prevValueKey = this.getValueKey(previousSelected); const prevItem = prevValueKey ? this.state.valuesKeys[prevValueKey] : null; return this.setState(nState,()=>{ ///vérifie s'il y a eu changement call on change par exemple let selectedItem = null; const valueKey = this.getValueKey(this.state.selected); if(this.state.initialized && !force){ if(!this.canHandleMultiple()){ if(valueKey && this.state.valuesKeys[valueKey]){ selectedItem = this.state.valuesKeys[valueKey].item; } if(this.compare(previousSelected,this.state.selected) && selectedItem && prevItem) { return; } } else { if(previousSelected === this.state.selected && previousSelected && prevItem) return; if(previousSelected.length == this.state.selected.length){ if(!this.state.selected.length) return; let areEquals = true; for(let i in this.state.selected){ let found = false; /*** pour chacune des nouvelles valeurs on vérifie s'il existe dans les précédentes valeurs */ for(let j in previousSelected){ if(this.compare(previousSelected[j],this.state.selected[i])){ found = true; break; } } /*** si on a pas trouvé alors les valeurs sont différentes */ if(!found){ areEquals = false; break; } } if(areEquals) return; } } } if(typeof this.props.onChange =="function" && stableHash(previousSelected) != stableHash(this.state.selected)){ this.props.onChange({value:this.state.selected,selectedKey:valueKey,selectedItems : this.getSelectedItems(),selectedItem,item:selectedItem,items:this.state.data}); } },force); } selectItem ({value,select,valueKey}){ let selected = this.canHandleMultiple() ? [...this.state.selected] : undefined; let selectedValuesKeys = {...this.state.selectedValuesKeys}; if(this.canHandleMultiple()){ if(!select){ if(valueKey in selectedValuesKeys){ const newS = []; delete selectedValuesKeys[valueKey]; for(let i in selected){ const vKey = this.getValueKey(selected[i]); if(vKey && vKey !== valueKey){ newS.push(selected[i]); } } selected = newS; } } else { if(!(valueKey in selectedValuesKeys)){ selectedValuesKeys[valueKey] = true; selected.push(value); } } } else { selected = select ? value : undefined; selectedValuesKeys = {}; if(select){ selectedValuesKeys[valueKey] = true; } } this.willHandleFilter = false; let nState = {}; if(!this.canHandleMultiple()){ nState.visible = false; } this.updateSelected({...nState,data:!this.isBigList?[...this.state.data]: this.state.data,selected,selectedValuesKeys}); } compare (value,currentValue){ if(this.getValueKey(value) === this.getValueKey(currentValue)) return true; if(typeof this.props.compare =='function'){ return this.props.compare(value,currentValue,{context:this,items:this.state.data}) } if(value === null || value ===""){ value = ""; } if(currentValue === null || currentValue =="") currentValue = ""; return NCompare(value,currentValue); } getCallArgs ({item,items,index,_index,...rest}) { return ({...rest,item,index,_index,counterIndex:_index,index,itemIndex:index,context:this,isDropdown:true,props:this.props,items,selectedColor:this.selectedColor,unselectedColor:theme.colors.text}); } isSelected (currentValue,valueKey,forceCheck,currentSelected){ if(valueKey && forceCheck !== true){ return valueKey in this.state.selectedValuesKeys ? true : false; } if(this.canHandleMultiple()) { currentSelected = Array.isArray(currentSelected)? currentSelected : this.state.selected; for(let i in currentSelected){ if(this.compare(currentValue,currentSelected[i])) return true; } return false; } else { return this.compare(currentValue,forceCheck?currentSelected:this.state.selected); } } prepareSelected({defaultValue}){ let s = defaultValue !== undefined ? defaultValue : undefined; if(this.canHandleMultiple()){ if(isNonNullString(s)){ s = s.split(",");//si c'est un tableau, ça doit être séparé de virgule } else { s = Array.isArray(s)? s : Object.toArray(s); } return Array.isArray(s)? s : []; } return s; } getNode({item,key,index,name,_index,callArgs}){ let content = this.renderItem(callArgs); const renderText = this.props.renderText; let text = React.getTextContent(typeof renderText ==='function'? renderText(callArgs) : undefined); if(content && !text){ text = React.getTextContent(content); } else if(!content || !React.isValidElement(content,true)){ content = text; } if(!React.isValidElement(content,true) || !content) { if(isWeb() || isObj(item)){ console.warn(content," is not valid element of dropdown name ",name,content,this.props,callArgs); } return null; } return { item, key, index, _index, text : React.getTextContent(text), textContent : React.getTextContent(content), content, } } /**** @param {object { selected, la nouvelle valeur sélectionnée selectedKeys {object}, les clés des valeurs sélectionnées }} */ getSelectedText (opts){ if(!isObj(opts)) return this.state.selectedText; let selectedValues = "selected" in opts ? opts.selected : this.state.selected; let selectedValuesKeys = isObj(opts.selectedValuesKeys) ? opts.selectedValuesKeys : isObj(this.state.selectedValuesKeys) ? this.state.selectedValuesKeys: {}; const valuesKeys = isObj(opts.valuesKeys) && Object.size(opts.valuesKeys,true)? opts.valuesKeys : isObj(this.state.valuesKeys)? this.state.valuesKeys: {}; let counter = 0,sDText = ""; const maxCount = MAX_SELECTED_ITEMS; for(let valueKey in selectedValuesKeys){ if(isObj(valuesKeys[valueKey])){ const node = valuesKeys[valueKey]; const text = node.text; if(!this.canHandleMultiple()){ sDText = text; } else { counter++; if(counter <= maxCount){ sDText+= (sDText?", ":"")+text; } } } } if(!sDText && selectedValues){ if(Array.isArray(selectedValues)){ for(let i in selectedValues){ const text = selectedValues[i]; if(!isNonNullString(text) && typeof text !=="number") continue; if(!this.canHandleMultiple()){ sDText = String(text); } else { counter++; if(counter <= maxCount){ sDText+= (sDText?", ":"")+text; } } } } else if(isNonNullString(selectedValues) || typeof selectedValues ==="number") { sDText = String(selectedValues); } this.toggleHasNoValidSelectedValue(!!sDText); } else { this.toggleHasNoValidSelectedValue(false); } if(this.canHandleMultiple() && counter > maxCount && sDText){ sDText+= ", et "+((counter-maxCount).formatNumber()+" de plus") } return sDText; } pushSelectedValue(value,selectedStateValue){ if(this.canHandleMultiple()){ selectedStateValue = Array.isArray(selectedStateValue)?selectedStateValue : []; selectedStateValue.push(value); return selectedStateValue; } return value; } getItemsData(args){ args = defaultObj(args); const itDatata = args.items ? args.items : this.props.items; const itemsData = typeof itDatata =='function'? itDatata(this.props) : itDatata; return itemsData; } prepareItems (args){ args = defaultObj(args); const nodes = {}; const data = []; const keys = []; const valuesKeys = {}; const {filter} = this.props; const currentSelected = this.prepareSelected({defaultValue:this.props.defaultValue,...args}); let selected = this.canHandleMultiple() ? []:undefined,selectedValuesKeys={}; const itemProps = defaultObj(this.props.itemProps); const itemsData = this.getItemsData(args); Object.map(itemsData,(item,index,_index)=>{ const key = this.getItemKey(item,index); const callArgs = this.getCallArgs({item,items:itemsData,index,_index}); const node = this.getNode({item,key,itemProps,index,_index,callArgs}) if(!node) { return null; } node.value = this.getItemValue(callArgs); const valueKey = this.getValueKey(node.value); if(!valueKey) { return null; } nodes[key] = node; node.valueKey = valueKey; valuesKeys[valueKey] = node; if(filter && filter({...callArgs,...nodes[key]}) === false){ return null; } if(typeof index =='number'){ _index = index; } if(this.isSelected(node.value,valueKey,true,currentSelected)){ selected = this.pushSelectedValue(node.value,selected); selectedValuesKeys[valueKey] = true; } else { delete selectedValuesKeys[valueKey]; } data.push(item); keys.push(key); }); return ({selected,selectedValuesKeys,currentSelected,selectedText:this.getSelectedText({selected,selectedValuesKeys,valuesKeys}),valuesKeys,nodes,valuesKeys,data,keys,initialized:true}); } getDefaultValue(){ return this.state.currentSelected; } getNodeFromValue (value){ const vKey= this.getValueKey(value); if(!vKey || !isObj(this.state.valuesKeys[vKey])){ return {}; } return {valueKey:vKey,node:this.state.valuesKeys[vKey]}; } /**** selectionne la valeur passée en paramètre * @param value {any} : la valeur à sélectionner * @param selectOnlyOneOne {boolean} spécifie si seul la valeur en question sera sélectionnée */ selectValue (value,selectOnlyOne) { let hasChanged = false; selectOnlyOne = selectOnlyOne === true ? true : false; let newSelected = undefined; if(this.canHandleMultiple()){ newSelected = selectOnlyOne ? [] : [...this.state.selected]; } const selectedValuesKeys = selectOnlyOne?{}:{...this.state.selectedValuesKeys}; const sVal = this.prepareSelected({defaultValue:value}); if(this.canHandleMultiple()){ if(sVal.length !== this.state.selected.length){ hasChanged = true; } for(let k in sVal){ const cVal = sVal[k]; const keyNode = this.getNodeFromValue(cVal); if(!keyNode.valueKey) continue; if(!selectedValuesKeys[keyNode.valueKey]){ newSelected.push(cVal); hasChanged = true; selectedValuesKeys[keyNode.valueKey] = true; } } } else { const keyNode = this.getNodeFromValue(value); if(!keyNode.valueKey) return; if(!selectedValuesKeys[keyNode.valueKey]){ newSelected = value; hasChanged = true; selectedValuesKeys[keyNode.valueKey] = true; } } if(hasChanged){ this.updateSelected({selected:newSelected,selectedValuesKeys}) } } selectAll (){ if(!this.canHandleMultiple()) return; const selected = [],selectedValuesKeys={}; this.state.data.map((item,_index)=>{ const key = this.keysRefs[_index]; if(!this.state.nodes[key]) return; selected.push(this.state.nodes[key].value); selectedValuesKeys[this.state.nodes[key].valueKey] = true; }); this.updateSelected({selected,selectedValuesKeys,selectedText:this.getSelectedText({selected,selectedValuesKeys})}); } unselectAll() { if(!this.canHandleMultiple()) return; this.updateSelected({selected:[],selectedValuesKeys:{},selectedText:''}) } unselect (oState){ this.updateSelected({...defaultObj(oState),selected:this.canHandleMultiple() ?[]:undefined,selectedValuesKeys:{},selectedText:""}); } getSelectedValue (){ return this.getSelected(); } getSelected (){ return this.state.selected; } getSelectedItems (){ let ret = {}; if(this.canHandleMultiple()){ Object.map(this.state.selected,(value)=>{ const nodeKey = this.getNodeFromValue(value); if(!nodeKey.valueKey) return; const node = nodeKey.node; ret[node.key] = node.item; }) return ret; } else { const nodeKey = this.getNodeFromValue(this.state.selected); if(!nodeKey.valueKey) return {}; const node = nodeKey.node; return {[node.key]:node.item}; } } refresh = (force,cb)=>{ if(isObj(this.props.context) && typeof this.props.context.refresh === "function"){ return this.props.context.refresh (force,cb); } if(force === true) { this.setState(this.prepareItems(),cb) return; } this.setState({sk:!this.state.sk},cb); } isSortable(){ return false && isObj(this.fieldsToSort)? true : false; } prepareSortableFields (){ let _sortFields = {},sortableFields = this.props.sortableFields; let hasSortableFields = false; if(isObj(sortableFields)){ Object.map(sortableFields,(sF,i)=>{ if(isNonNullString(sF)){ _sortFields[i] = sF; hasSortableFields = true; } }) } if(!hasSortableFields){ _sortFields = undefined; } return _sortFields; } sort (items,update,sortDir){ let column = sorting.column; if(this.isSortable() && column){ let dir = sorting.dir; let sortDir = defaultStr(sortDir).toLowerCase(); if(sortDir =="asc" || sortDir =="desc"){ dir = sortDir; } else { if(this.hasAlreadySort && column === this.state.sortableField){ dir = dir =="asc"? "desc" : "asc"; } else dir = "asc"; } this.hasAlreadySort = true; items = sortBy(items,{column,dir:dir,returnArray:true}); if(update !== false && (column !== this.state.sortableField || dir !== this.state.sortableDir)){ this._renderedItems = undefined; let endItemsIndex = this.getMaxItemsToRender(); this.prepareFetchedItems(items); this.hasFetchNewItems = true; this.setState({menuItems:items,endItemsIndex,hasMore:this.canFetchMoreItems(items,endItemsIndex),sortableField : column,sortableDir:dir}) return items; } } return items; } onLayout (event) { const layout = event.nativeEvent.layout; const isMob = isMobileOrTabletMedia(); const prevLayout = this.state.layout; const prevIsMob = this.state.isMobileMedia; const prevH = Math.abs(prevLayout.height-layout.height), prevW = Math.abs(prevLayout.width,layout.width); if(prevIsMob === isMob && prevH <= 20 && prevW <= 50) return; this.updateVisibleState({ isMobileMedia : isMobileOrTabletMedia(), layout, }); } isLoading(){ return this.props.isLoading === true ? true : false } open (force,cb){ let u = force; if(typeof force =='function'){ u = cb; cb = force; if(typeof u =='boolean'){ force = u; } } force = typeof force !== 'boolean'? false : force; if(this.props.disabled === true || this.props.readOnly === true || (force !== true && this.isLoading())) return; if(!this.state.visible){ if(this.props.withBottomSheet){ getContentHeight(this.anchorRef).then(({height})=>{ this.updateVisibleState({visible:true,anchorHeight:height},cb) }) } else { this.updateVisibleState({visible:true},cb); } } } show(force,cb){ return this.open(force,cb); } updateVisibleState(state,cb){ state = defaultObj(state); const visible = this.state.visible; this.setState(state,()=>{ const arg = {context:this,selected:this.state.selected,visible:this.state.visible}; if(visible !== this.state.visible){ if(!this.state.visible){ if(typeof this.props.onDismiss =='function'){ this.props.onDismiss(arg); } } else if(typeof this.props.onOpen =='function'){ this.props.onOpen(arg); } } if(typeof cb =='function'){ cb (arg); } }) } hide (cb){ return this.updateVisibleState({visible:false},cb); } close (cb){ return this.hide(cb); } canHandleFilter(){ return this.props.disabled !== true && this.props.readOnly !==true && this.state.visible ? true : false; } focus = ()=>{ if(this.canHandleFilter() && this.inputRef && this.inputRef.current){ if(this.inputRef.current.focus){ this.inputRef.current.focus(); } else if(this.inputRef.current.forceFocus){ this.inputRef.current.forceFocus(); } } } runSearchFilter (text){ clearTimeout(this.doSearchFilter); if(!this.state.visible) { if(this.state.isFiltering){ this.setState({isFiltering:false}); } return; } this.setState({isFiltering:true,filterText:text}); } getItems (){ if(!this.state.visible || this.isLoading()){ return []; } if(this.canHandleFilter() && this.state.isFiltering && this.state.filterText){ const filterRegex = new RegExp(this.state.filterText.replace(matchOperators, '\\$&'), 'gi'); this.keysRefs = []; return this.state.data.filter((item,_index)=>{ const key = this.state.keys[_index]; if(!isObj(this.state.nodes[key])) return false; if(this.state.nodes[key].textContent.match(filterRegex)){ this.keysRefs.push(key); return true; } return false; }) } this.keysRefs = this.state.keys; return this.state.data; } componentWillUnmount(){ super.componentWillUnmount(); if(typeof this.props.onUnmount =="function"){ this.props.onUnmount({context:this,selected:this.state.selected,items:this.state.data}) } } componentDidMount(){ super.componentDidMount(); if(typeof this.props.onMount =='function'){ this.props.onMount({context:this,selected:this.state.selected,items:this.state.data}); } } UNSAFE_componentWillReceiveProps(nextProps){ const {items,defaultValue,selected} = nextProps; const isFunc = typeof nextProps.items == "function"; if(nextProps.isLoading === true) return; const isThirdParty = this.props.name == "thirdParty"; if(isFunc || !React.areEquals(items,this.props.items)){ const nState = this.prepareItems({items,defaultValue,selected}); return this.updateSelected(nState,!isFunc); } let value = this.prepareSelected({defaultValue}); let areEquals = !this.canHandleMultiple() ? this.compare(value,this.state.selected) : false; if(areEquals) { return; } let selectedValuesKeys = {}, newSelected = this.canHandleMultiple() ? [] : value; if(this.canHandleMultiple()){ areEquals = value.length === this.state.selected.length; if(areEquals && !value.length){ areEquals = true; } else for(let i in value){ const valueKey = this.getValueKey(value[i]); if(valueKey){ if(!selectedValuesKeys[valueKey]){ newSelected.push(value[i]); } selectedValuesKeys[valueKey] = true; if(areEquals && !this.state.selectedValuesKeys[valueKey]){ areEquals = false; } } } } else { const valueKey = this.getValueKey(value); if(valueKey){ selectedValuesKeys[valueKey] = true; newSelected = value; } else { newSelected = undefined; selectedValuesKeys = {}; if(!areEquals){ areEquals = this.state.selected === undefined || this.state.selected ===''? true : false; } } } if(areEquals) return; this.updateSelected({ selectedValuesKeys, selected:newSelected, selectedText : this.getSelectedText({selected:newSelected,selectedValuesKeys}) }); } isVisible(){ return this.state.visible; } getBackgroundColor(){ return theme.surfaceBackground; } render (){ let { multiple:_multiple, itemContainerProps, onDismiss, onOpen, onClose, value, selected, visible: _visible, itemProps, disabled, readOnly, defaultValue, selectedColor, label, display, text, placeholder, inputProps, items, getItemKey, helperText, error, onChange, onMount, filter, onUnmount, itemValue, name, anchorChildren, compare : _compare, renderItem, listProps, dynamicContent, dropdownActions, renderText, sortableFields, isLoading, progressBarProps, addIcon, addIconTooltip, addIconProps, showAdd, tagProps, onAdd, showSearch, onAddCallback, onAddPress, onAddProps, itemLabel, checkedIcon:customCheckedIcon, mode, withBottomSheet, context : contextProps, getValueKey, bindResizeEvents, left, right, backgroundColor : cBackgroundColor, dialogProps, testID, ...dropdownProps } = this.props; testID = defaultStr(testID,"RN_DropdownComponent"); const flattenStyle = StyleSheet.flatten(dropdownProps.style) || {}; itemContainerProps = defaultObj(itemContainerProps); dropdownProps = defaultObj(dropdownProps); const multiple = this.canHandleMultiple(); const renderTag = multiple && (display == 'tags' || display === 'tag' )? true : false; this.willRenderTag = renderTag; itemProps = defaultObj(itemProps); disabled = defaultBool(disabled,false); readOnly = defaultBool(readOnly,false); listProps = defaultObj(listProps); selectedColor = (Colors.isValid(selectedColor)? selectedColor : theme.colors.primaryOnSurface); this.selectedColor = selectedColor; const {layout:inputLayout,selectedText,visible,isFiltering,filterText} = this.state; const self = this,state = this.state; const canHandle = !this.isLoading(); const canFilter = !disabled && !readOnly && visible; const isMob = isMobileOrTabletMedia(); inputProps = defaultObj(inputProps); const contentContainerProps = Object.assign({},inputProps.contentContainerProps); const containerProps = Object.assign({},inputProps.containerProps); const inputRest = {disabled,readOnly,label,error} clearTimeout(this.doSearchFilter); this.doSearchFilter = null; mode = defaultStr(mode,inputProps.mode); const textInputProps = { ...inputRest, mode, disabled, style : StyleSheet.flatten([styles.input,inputProps.style]) } const dimensions = Dimensions.get("window"); let contentContainerHeight = dimensions.height - defaultDecimal(inputLayout?.top) - defaultDecimal(inputLayout?.height)-20; contentContainerHeight = Math.max(contentContainerHeight,200); if(isMob){ contentContainerHeight = '95%'; } const iconDisabled = !canHandle || disabled || readOnly ?true : false; const pointerEvents = iconDisabled?"none":"auto"; addIconTooltip = defaultStr(addIconTooltip,'Ajouter un élément'); addIconProps = defaultObj(addIconProps); if(disabled || readOnly){ showAdd = false; } if(typeof showAdd ==='function'){ showAdd = showAdd(this.props); } showAdd = defaultBool(showAdd,false); if(addIcon ===false) { showAdd = false; } const addIconColor = Colors.isValid(addIconProps.color)? addIconProps.color : Colors.toAlpha(theme.colors.text,theme.ALPHA); const _addIconProps = { icon : isNonNullString(addIcon)?addIcon:'plus-thick', tooltip : addIconTooltip, ...addIconProps, color : addIconColor, onPress : (e)=>{ React.stopEventPropagation(e); if(iconDisabled) return; const onAddP = typeof onAddProps ==='function'? onAddProps(this.props) : onAddProps; const aArgs = {...React.getOnPressArgs(e),...Object.assign({},onAddP),isMobile:isMob,context:this,visible:state.visible,field:name,props:this.props}; if(onAdd){onAdd(aArgs);} else if(onAddPress){ onAddPress(aArgs) } }, disabled : iconDisabled } let menuActions = []; Object.map(dropdownActions,(action,index)=>{ if(!isObj(action) || (!action.text && !!action.label)) return; action.text = defaultVal(action.text,action.label); menuActions.push(action); }); if(this.isSortable()){ menuActions.push( { text : 'Trier par', icon : "sort", items : Object.mapToArray(sortableFields,(f,i)=>{ return { text : f, icon : i == sorting.column ? (sorting.dir !="desc"?"sort-ascending":"sort-descending"):"", onPress : x =>{ React.stopEventPropagation(x); this.sort(i); } } }) }) } if(canFilter && filterText){ menuActions.push({ icon : 'close', onPress : ()=>{ this.setState({filterText:''}); }, text : 'Effacer le texte', }); } if(multiple){ menuActions.push( { text : 'Tout sélectionner', icon : 'checkbox-multiple-marked', onPress : this.selectAll.bind(this), }); menuActions.push({ text : 'Tout Désélectionner', icon : 'checkbox-multiple-blank-outline', onPress : this.unselectAll.bind(this), }); } else if(!multiple && state.selected !== undefined){ menuActions.push( { text : 'Désélectionner', icon : 'select', onPress : this.unselect.bind(this) }); } if(renderTag){ tagProps = defaultObj(tagProps); } error = error || this.hasNoValidSelectedValue() || false; if(error && selectedText && (!helperText || !React.isValidElement(helperText,true))){ helperText = `Ce champ admet des valeurs par défaut invalide où innexistant dans la liste des éléments à sélectionner`; console.warn("dropdown has invalid value for items ",error,", selectedText = ",selectedText," selected from state = ",this.state.selected,", state is ",this.state,", props is ",this.props); } helperText = <HelperText disabled={disabled} error={error}>{helperText}</HelperText> let labelTextField = defaultVal(label,text); const isFlatMode = textInputProps.mode === flatMode; const dropdownStyle = StyleSheet.flatten(dropdownProps?.style); let backgroundColor = Colors.isValid(cBackgroundColor)? cBackgroundColor : Colors.isValid(dropdownStyle?.backgroundColor)? dropdownStyle?.dropdownStyle: Colors.isValid(textInputProps.style.backgroundColor)?textInputProps.style.backgroundColor : Colors.isValid(flattenStyle.backgroundColor)? flattenStyle.backgroundColor : this.getBackgroundColor(); const tagLabelStyle = {backgroundColor,color:Colors.setAlpha(theme.colors.text,theme.ALPHA)} if(!isFlatMode && backgroundColor ==='transparent'){ tagLabelStyle.backgroundColor = this.surfaceBackground(); } textInputProps.style.backgroundColor = backgroundColor; progressBarProps = defaultObj(progressBarProps); const loadingElement = !canHandle && !this.props.isFilter ? (<View testID={testID+"_DropdownActivityIndicatorContainer"} style = {[{paddingRight : 20}]}> <ActivityIndicator color={error?theme.colors.error:theme.colors.secondary} animating={true} testID={testID+"_DropdownActivityIndicator"} {...progressBarProps} /> </View>): null; const tagsContent = renderTag ? <View style={[styles.tagsContent]} testID={testID+"_TagsContainerWrapper"}> {state.selected.map((value,i)=>{ const nodeKey = this.getNodeFromValue(value); if(!nodeKey.valueKey) return null; const valueKey = nodeKey.valueKey; const {text} = nodeKey.node; const p = Colors.getAvatarStyleFromSuffix(i+1); return <Chip {...tagProps} style = {[p.style,{color:p.color,marginBottom:5,marginRight:5},tagProps.style]} textStyle = {[{color:p.color},tagProps.textStyle]} key = {i} onPress = {()=>{ this.selectItem({value,valueKey,select:false}); }} onClose = {()=>{ this.selectItem({value,valueKey,select:false}); }} >{text} </Chip>; })} </View> : null; const defRight = defaultVal(textInputProps.right,inputProps.right); const enableCopy = defaultBool(inputProps.enableCopy,textInputProps.enableCopy,(iconDisabled || (!multiple && !showAdd)) && !loadingElement ?true : false); const anchor = <TouchableRipple onPress={this.open.bind(this)} disabled = {disabled || isWeb()} onLayout={bindResizeEvents === false ? undefined : this.onLayout.bind(this)} style = {{pointerEvents}} aria-label={defaultStr(dropdownProps["aria-label"],label,text)} testID = {testID} > <View {...dropdownProps} {...contentContainerProps} style={[contentContainerProps.style,{pointerEvents},flattenStyle]} ref = {this.anchorRef} collapsable = {false} > {<TextField defaultValue={selectedText} autoHeight = {renderTag} useReadOnlyOpacity = {!disabled && !readOnly ? false : true} {...inputProps} {...textInputProps} mode = {mode} enableCopy = {enableCopy} label = {labelTextField} style = {[inputProps.style,textInputProps.style,{pointerEvents:"none"}]} disabled = {disabled} readOnly = {true} alwaysUseLabel = {renderTag?true : false} contentContainerProps = {{ ...contentContainerProps, style : [renderTag? styles.inputContainerTag:null,{pointerEvents:iconDisabled && (!enableCopy && disabled)?'none':'auto'},styles.anchorContentContainer,contentContainerProps.style], }} containerProps = {{...containerProps,style:[containerProps.style,styles.mbO]}} error = {!!error} right = {loadingElement ? loadingElement : disabled? null : (props)=>{ let r = React.isValidElement(defRight)?<>{defRight}</> : <></>; if(typeof defRight =='function'){ const t = defRight(props); r = React.isValidElement(t)? r = <>{t}{r}</> : r; } if(React.isValidElement(this.props.right)){ r = <>{this.props.right}{r}</> } else if(typeof this.props.right =='function'){ const t = this.props.right(props); r = React.isValidElement(t)? r = <>{t}{r}</> : r; } if(showAdd){ return <>{r}<Icon {..._addIconProps} {...props} style={[theme.styles.noMargin,theme.styles.noPadding,_addIconProps.style,props.style]}/></> } return r; }} onPress = {this.open.bind(this)} placeholder={placeholder} render = {!renderTag?inputProps.render : (tagProps)=>{ return <View style={[styles.tagsContentContainer,{pointerEvents},isFlatMode?styles.tagsContentContainerFlatMode:null]}> {tagsContent} </View> }} helperText = {''} children = {anchorChildren} />} {!canHandle && isFlatMode && <ProgressBar color={theme.colors.secondary} {...defaultObj(progressBarProps)} indeterminate />} {helperText} </View> </TouchableRipple> let restProps = {}; if(!isMob){ restProps.withScrollView = false; restProps.sameWidth = true; } else { restProps.fullScreen = true; restProps.maxActions = 0; restProps.actions = [{ icon : 'check', text : 'Fermer', onPress : this.close.bind(this), }] } const Component = withBottomSheet === true ? BottomSheet : isMob ? Dialog : Menu ; const MComponent = withBottomSheet === true ? BottomSheetMenu : MenuComponent; if(withBottomSheet){ restProps.controlled = true; restProps.height = this.state.anchorHeight; restProps.withScrollView = false; restProps.pointerEvents = "auto"; } const renderingItems = this.getItems(); const isDisabled = readOnly || disabled?true:false; const isBigList = this.isBigList; const autoFocus = canAutoFocusSearchField({visible,items:renderingItems}); dialogProps = defaultObj(dialogProps); if(this.props.name =="RG_Compta"){ restProps.testMeCompta = true; } return ( <Fragment> {!withBottomSheet && isMob && anchor} <Component dismissable {...restProps} testID = {testID+"_ModalComponent"} withScrollView = {false} visible={visible} onDismiss={this.hide.bind(this)} contentStyle = {[{paddingVertical:0},restProps.contentStyle]} anchor={anchor} {...dialogProps} title = {defaultStr(dialogProps.title,label,text)+" [ "+self.state.data.length.formatNumber()+" ]"} subtitle = {selectedText||'Aucun élément sélectionné'} style = {[restProps.style]} contentProps = {{style:{flex:1}}} > <View style={[ styles.contentWrapper, { //paddingRight : 0, //paddingLeft : !isMob ? 5 : undefined, height : !isMob?contentContainerHeight:'90%', }, isMob && {flex:1}, {pointerEvents} ]} testID = {testID+"_Container"} > {showSearch !== false && <> <TextField testID = {testID+"_SearchField"} affix = {false} {...textInputProps} dynamicBackgroundColor = {false} mode = {flatMode} disabled = {iconDisabled} outlined = {false} defaultValue = {filterText} containerProps = {{style:styles.searchContainer}} contentContainerProps = {{style:[styles.inputContainer]}} placeholder = {"rechercher ["+self.state.data.length.formatNumber()+"]"} label = {""} error = {error} style = {[styles.searchInput,textInputProps.style,{backgroundColor:'transparent'}]} ref = {this.inputRef} autoFocus = {autoFocus} onMount = {()=>{ if(autoFocus){ this.focus(); } }} onChangeText = {debounce((text)=>{ if(!text && !multiple){ return this.unselect({filterText:''}); } return this.runSearchFilter(text); },getSearchTimeout(this.state.data.length))} left = {(props)=><Icon testID = {testID+"_Left"} icon={'magnify'} {...props} style={[styles.left,props.style]} />} right = {(props)=>{ r