@fto-consult/expo-ui 2.27.0 → 2.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fto-consult/expo-ui",
3
- "version": "2.27.0",
3
+ "version": "2.28.0",
4
4
  "description": "Bibliothèque de composants UI Expo,react-native",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -61,7 +61,7 @@
61
61
  "@emotion/native": "^11.10.0",
62
62
  "@expo/html-elements": "^0.2.0",
63
63
  "@expo/vector-icons": "^13.0.0",
64
- "@fto-consult/common": "^1.24.16",
64
+ "@fto-consult/common": "^1.25.1",
65
65
  "@gorhom/portal": "^1.0.14",
66
66
  "@react-native-async-storage/async-storage": "~1.17.3",
67
67
  "@react-native-community/datetimepicker": "6.5.2",
@@ -10,7 +10,7 @@ import {isWeb,isNativeMobile} from "$cplatform";
10
10
  import Divider from "$ecomponents/Divider";
11
11
  import {isMobileOrTabletMedia} from "$cplatform/dimensions";
12
12
  import APP from "$capp/instance";
13
- import KeyboardAvoidingView from "$ecomponents/Form/KeyboardAvoidingView";
13
+ import KeyboardAvoidingView from "$ecomponents/KeyboardAvoidingView";
14
14
  import {Elevations} from "$ecomponents/Surface";
15
15
  import {defaultStr} from "$utils";
16
16
  import View from "$ecomponents/View";
@@ -258,11 +258,6 @@ const BottomSheetComponent = React.forwardRef((props,ref)=> {
258
258
  activeOpacity={1}
259
259
  onPress={() => (closeOnPressMask && dismissable !== false ? closeModal() : null)}
260
260
  />
261
- <KeyboardAvoidingView
262
- behavior="padding"
263
- style={[styles.wrapper]}
264
- testID = {testID+"_KeyboardAvoidingView"}
265
- >
266
261
  <Animated.View
267
262
  {...(!dragFromTopOnly && panResponder.panHandlers)}
268
263
  testID = {testID+"_Container"} {...containerProps}
@@ -289,19 +284,17 @@ const BottomSheetComponent = React.forwardRef((props,ref)=> {
289
284
  </View>
290
285
  <Divider testID = {testID+"_Divider"} style={styles.divider}/>
291
286
  </> : null}
292
- {withScrollView !== false ? <>
287
+ {withScrollView !== false ?
293
288
  <ScrollView testID = {testID+"_ScrollViewContent"} contentProps = {{style:styles.scrollViewContent}} {...scrollViewProps} style={[styles.scrollView,scrollViewProps.style]} alwaysBounceVertical={false}
294
- contentContainerStyle={[{ flexGrow: 1,margin:0,paddingBottom:30},scrollViewProps.contentContainerStyle]}
289
+ contentContainerStyle={[{ flexGrow: 1,margin:0,paddingBottom:30},scrollViewProps.contentContainerStyle]}
295
290
  >
296
- {children}
297
- </ScrollView>
298
- </>
291
+ <KeyboardAvoidingView testID={testID+"_KeyboardAvoidingView"}>{children}</KeyboardAvoidingView>
292
+ </ScrollView>
299
293
  : <View testID = {testID+"_Children"} {...childrenContainerProps} style={[styles.childrenNotScroll,childrenContainerProps.style]}>
300
- {children}
294
+ <KeyboardAvoidingView testID={testID+"_KeyboardAvoidingView"}>{children}</KeyboardAvoidingView>
301
295
  </View>}
302
296
  </View>
303
297
  </Animated.View>
304
- </KeyboardAvoidingView>
305
298
  </View>
306
299
  </Portal>
307
300
  );
@@ -28,7 +28,7 @@ import { evalSingleValue,Footer,getFooterColumnValue,isValidAggregator,extendAgg
28
28
  import i18n from "$i18n";
29
29
  import { makePhoneCall,canMakePhoneCall as canMakeCall} from "$makePhoneCall";
30
30
  import copyToClipboard from "$capp/clipboard";
31
- import { Pressable,PanResponder } from "react-native";
31
+ import { Pressable } from "react-native";
32
32
  import TableLink from "$TableLink";
33
33
  import appConfig from "$capp/config";
34
34
  import stableHash from "stable-hash";
@@ -56,6 +56,7 @@ const DatePickerInput = React.forwardRef(({
56
56
  if(isPeriodAction){
57
57
  return <PeriodActionComponent
58
58
  {...rest}
59
+ style = {style}
59
60
  left = {left}
60
61
  defaultValue = {defaultValue}
61
62
  right = {right}
@@ -0,0 +1,24 @@
1
+ // Copyright 2022 @fto-consult/Boris Fouomene. All rights reserved.
2
+ // Use of this source code is governed by a BSD-style
3
+ // license that can be found in the LICENSE file.
4
+ import React from "$react";
5
+ import TextField from "$ecomponents/Form/Fields/TextField";
6
+ import {defaultObj} from "$utils";
7
+
8
+ const FilterBetweenComponent = React.forwardRef(({inputProps,contentContainerProps,...props},ref)=>{
9
+ contentContainerProps = defaultObj(contentContainerProps);
10
+ contentContainerProps.pointerEvents = "auto";
11
+ return <TextField
12
+ ref = {ref}
13
+ {...props}
14
+ contentContainerProps={contentContainerProps}
15
+ inputProps = {{...defaultObj(inputProps),pointerEvents:"none",editable:false}}
16
+ type ="text"
17
+ format = {undefined}
18
+ onChange = {(e)=>false}
19
+ onValidate = {(e)=>false}
20
+ />
21
+ });
22
+
23
+ FilterBetweenComponent.displayName = "FilterBetweenComponent";
24
+ export default FilterBetweenComponent;
@@ -1,8 +1,8 @@
1
1
 
2
2
 
3
3
  import DateLib from "$lib/date";
4
- import {isNonNullString,defaultStr,isNullOrEmpty,debounce,uniqid} from "$utils";
5
- import {regexParser,regexActions,operators as _operators,actions as _actions,periodActions, inActions as _inActions,getFilterStateValues,getSessionData,setSessionData} from "$cfilters";
4
+ import {isNonNullString,defaultStr,isNullOrEmpty,debounce,isFunction,uniqid} from "$utils";
5
+ import {regexParser,regexActions,operators as _operators,actions as _actions,periodActions,betweenActions, inActions as _inActions,getFilterStateValues,getSessionData,setSessionData} from "$cfilters";
6
6
  import {parseDecimal} from "$ecomponents/TextField";
7
7
  import notify from "$notify";
8
8
  import PropTypes from "prop-types";
@@ -16,6 +16,7 @@ import theme from "$theme";
16
16
  import {isMobileMedia} from "$cplatform/dimensions";
17
17
  import { ActivityIndicator } from "react-native-paper";
18
18
  import DialogProvider from "$components/Form/FormData/DialogProvider";
19
+ import FilterBetweenComponent from "./BetweenComponent";
19
20
 
20
21
  const manualRunKey = "manual-run";
21
22
 
@@ -129,10 +130,19 @@ export default class Filter extends AppComponent {
129
130
  this.fireFilterSearch(false);
130
131
  });
131
132
  }
133
+ getDefaultAction(type){
134
+ type = defaultStr(type,this.type,this.props.type).toLowerCase();
135
+ if(type.contains('select')){
136
+ return "$in";
137
+ } if(type !== 'date2time' && type !== 'time' && type !== 'number' && type !== 'decimal'){
138
+ return '$regexcontains';
139
+ }
140
+ return '$eq';
141
+ }
132
142
  initFiltersOp(type){
133
143
  type = defaultStr(type,this.type).toLowerCase();
134
144
  let operators = {..._operators};
135
- let actions = {..._actions};
145
+ let actions = {...betweenActions,..._actions};
136
146
  if(this.props.orOperator === false){
137
147
  delete operators.$or;
138
148
  }
@@ -144,21 +154,18 @@ export default class Filter extends AppComponent {
144
154
  let ignoreCase = this.props.ignoreCase;
145
155
  ignoreCase = defaultVal(ignoreCase,true);
146
156
  let isTextFilter = false;
147
- let defaultAct = '$eq';
148
157
  if(type =="checkbox" || type == 'switch'){
149
158
  action = '$eq';
150
- } else if(type == 'select'){
159
+ } else if(type.contains('select')){
151
160
  actions = _inActions;
152
- defaultAct = "$in";
153
161
  } else if(type == 'date' || type =='datetime') {
154
162
  actions = {...periodActions, ...actions}
155
163
  } else if(type !== 'date2time' && type !== 'time' && type !== 'number' && type !== 'decimal'){
156
- actions = regexActions;
157
- defaultAct = '$regexcontains';
164
+ actions = {...betweenActions,...regexActions};
158
165
  isTextFilter = true;
159
166
  }
160
167
  if(!action){
161
- action = defaultAct;
168
+ action = this.getDefaultAction(type);
162
169
  }
163
170
  operator = defaultVal(operator,"$and");
164
171
  return {actions,action,ignoreCase,operator,operators,manualRun:defaultBool(this.props.manualRun,false),defaultValue:defaultVal(this.props.defaultValue),isTextFilter};
@@ -248,26 +255,44 @@ export default class Filter extends AppComponent {
248
255
  const t = defaultStr(this.type,this.props.type);
249
256
  return t.contains("date") && t.contains("time");
250
257
  }
251
- showPeriodSelector (success){
258
+ showBetweenSelector(args){
259
+ return this.showBetweenActionSelector(args);
260
+ }
261
+ showPeriodSelector(args){
262
+ return this.showBetweenActionSelector(args);
263
+ }
264
+ showBetweenActionSelector(args){
265
+ if(typeof args =='function'){
266
+ args = {success:args};
267
+ }
268
+ let {success,callback} = defaultObj(args);
269
+ const _type = defaultStr(this.type,this.props.type).toLowerCase().trim();
270
+ success = typeof success =='function'? success : typeof callback =='function'? callback : undefined;
252
271
  const defaultValue = defaultStr(this.state.defaultValue).trim();
253
272
  let split = defaultValue.split("=>");
254
- let isDateTime = this.isDateTime();
255
- let start = isDateTime ? new Date().toSQLDateTimeFormat() : new Date().toSQLDateFormat(), end = start;
256
-
257
- if(DateLib.isValidSQLDateTime(split[0]) || DateLib.isValidSQLDate(split[0])){
258
- start = split[0];
259
- }
260
- if(DateLib.isValidSQLDateTime(split[1]) || DateLib.isValidSQLDate(split[1])){
261
- end = split[1];
262
- }
263
- const type = isDateTime? "datetime" : "date";
264
- DialogProvider.open({
273
+ const isDateTime = this.isDateTime();
274
+ let start = split[0] && split[0] || undefined, end = split[1] && split[1] || undefined;
275
+ const willHandleDate = _type.contains('date');
276
+ if(willHandleDate){
277
+ start = isDateTime ? new Date().toSQLDateTimeFormat() : new Date().toSQLDateFormat();
278
+ end = start;
279
+ if(DateLib.isValidSQLDateTime(split[0]) || DateLib.isValidSQLDate(split[0])){
280
+ start = split[0];
281
+ }
282
+ if(DateLib.isValidSQLDateTime(split[1]) || DateLib.isValidSQLDate(split[1])){
283
+ end = split[1];
284
+ }
285
+ }
286
+ const type = willHandleDate ? (isDateTime? "datetime" : "date"):_type;
287
+ const format = this.props.format;
288
+ return new Promise((resolve,reject)=>{
289
+ DialogProvider.open({
265
290
  subtitle : false,
266
291
  fields : {
267
- start : {type,text:'Du',defaultValue:start},
268
- end : {type,text:'Au',defaultValue:end}
292
+ start : {format,type,text:(willHandleDate?'Du':"Valeur inférieure"),defaultValue:start},
293
+ end : {format,type,text:willHandleDate?'Au':"Valeur supérieure",defaultValue:end}
269
294
  },
270
- title :"Définir une période ["+defaultStr(this.props.label,this.props.text)+"]",
295
+ title :"Définir {0} [{1}]".sprintf(willHandleDate?"une période":"Un intervalle",defaultStr(this.props.label,this.props.text)),
271
296
  cancelButton : true,
272
297
  actions : {
273
298
  yes : {
@@ -276,19 +301,22 @@ export default class Filter extends AppComponent {
276
301
  },
277
302
  },
278
303
  onSuccess : ({data})=>{
279
- if(data.start && data.end && data.start> data.end){
280
- return notify.error("La date de fin doit être supérieure à la date de début");
304
+ if(data.start !== undefined && data.start !== null && data.end !== undefined && data.end !== null && data.start> data.end){
305
+ return notify.error("La {0} doit être supérieure à la {1}".sprintf(willHandleDate?"date de fin":"valeur supérieure",willHandleDate?"date de début":"valeur inférieure"));
281
306
  }
282
- if(isFunction(success)){
307
+ if(typeof(success) =="function"){
283
308
  success(data.start+"=>"+data.end);
284
309
  }
310
+ resolve(data.start+"=>"+data.end);
285
311
  DialogProvider.close();
286
312
  return true;
287
- }})
313
+ }
314
+ })
315
+ })
288
316
  }
289
317
  setAction(action,text){
290
318
  if(!(this.searchFilter.current)) return;
291
- if(action === this.state.action && action !="$period" && action !== "$today" && action !='$yesterday') return;
319
+ if(action === this.state.action && !periodActions[action] && !betweenActions[action]) return;
292
320
  let value = this.state.defaultValue;
293
321
  let act = defaultStr(action).toLowerCase();
294
322
  const isDateTime = this.type?.contains("time");
@@ -297,6 +325,10 @@ export default class Filter extends AppComponent {
297
325
  this.showPeriodSelector((d)=>{
298
326
  this.runAction({value:d,action});
299
327
  })
328
+ } else if(action == '$between'){
329
+ this.showBetweenSelector((d)=>{
330
+ this.runAction({value:d,action});
331
+ })
300
332
  } else if(action =="$today"){
301
333
  return this.runAction({value:new Date().resetHours().resetMinutes().resetSeconds().toFormat(dateFormat),action})
302
334
  } else if(action =="$yesterday"){
@@ -346,7 +378,7 @@ export default class Filter extends AppComponent {
346
378
  })
347
379
  }
348
380
  clearFilter(event){
349
- this.setState({defaultValue:undefined},()=>{
381
+ this.setState({defaultValue:undefined,action:this.getDefaultAction()},()=>{
350
382
  this.callOnValidate();
351
383
  this.fireValueChanged(true);
352
384
  let {onClearFilter,onResetFilter} = this.props;
@@ -356,6 +388,33 @@ export default class Filter extends AppComponent {
356
388
  }
357
389
  })
358
390
  }
391
+ isBetweenAction(action){
392
+ action = defaultStr(action,this.state.action).toLowerCase();
393
+ return !!(this.state.actions && betweenActions[action]);
394
+ }
395
+ isPeriodAction(action){
396
+ action = defaultStr(action,this.state.action).toLowerCase();
397
+ return !!(this.state.actions && periodActions[action]);
398
+ }
399
+ formatValue(value){
400
+ const type = defaultStr(this.type,this.props.type).toLowerCase();
401
+ if(this.isBetweenAction() && isNonNullString(value) && (type ==="number" || type =="decimal")){
402
+ const sp = value.split("=>");
403
+ if(sp.length ==2 && type){
404
+ const format = defaultStr(this.props.format).toLowerCase();
405
+ const v1 = defaultDecimal(parseDecimal(sp[0],type));
406
+ const v2 = defaultDecimal(parseDecimal(sp[1],type));
407
+ return format =="money"? (v1.formatMoney()+"=>"+v2.formatMoney()) : (v1.formatNumber()+"=>"+v2.formatNumber());
408
+ }
409
+ }
410
+ if(typeof value =='number'){
411
+ if(this.props.format =='money'){
412
+ return value.formatMoney();
413
+ }
414
+ return value.formatNumber();
415
+ }
416
+ return value;
417
+ }
359
418
  render (){
360
419
  let {
361
420
  filter,
@@ -442,8 +501,9 @@ export default class Filter extends AppComponent {
442
501
  if(isFunction(filter)){
443
502
  rest.filter = filter;
444
503
  }
445
- const isPeriodAction = this.state.actions && periodActions[this.state.action]
446
- const ignoreDefaultValue = isPeriodAction && isNonNullString(defaultValue) && defaultValue.contains("=>");
504
+ const isPeriodAction = this.isPeriodAction();
505
+ const isBetweenAction = this.isBetweenAction();
506
+ const ignoreDefaultValue = (isPeriodAction||isBetweenAction) && isNonNullString(defaultValue) && defaultValue.contains("=>");
447
507
  rest.defaultValue = defaultValue;
448
508
  rest.disabled = rest.readOnly = rest.affix = false;
449
509
  rest.editable = true;
@@ -500,7 +560,7 @@ export default class Filter extends AppComponent {
500
560
  style : [styles.bold,styles.noVerticalPadding],
501
561
  }:null],
502
562
  ...Object.mapToArray(actions,(x,j)=>{
503
- if(ignoreDefaultValue && !periodActions[j]){
563
+ if(ignoreDefaultValue && !periodActions[j] && !betweenActions[j]){
504
564
  return null;
505
565
  }
506
566
  let checked = j === action?true : false;
@@ -523,7 +583,7 @@ export default class Filter extends AppComponent {
523
583
  }
524
584
 
525
585
  if(!hasS){
526
- x = x+" <"+defaultValue+">"
586
+ x = x+" <"+this.formatValue(defaultValue)+">"
527
587
  }
528
588
  }
529
589
  return {
@@ -539,10 +599,10 @@ export default class Filter extends AppComponent {
539
599
  const containerProps = defaultObj(this.props.containerProps,rest.containerProps);
540
600
  delete rest.containerProps;
541
601
  rest.onValidate = this.onFilterValidate.bind(this);
542
- const Component = this.Component;
602
+ const Component = isBetweenAction ? FilterBetweenComponent : this.Component;
543
603
  const responsiveProps = Object.assign({},responsiveProps);
544
604
  responsiveProps.style = [theme.styles.w100,responsiveProps.style]
545
- if(ignoreDefaultValue) {
605
+ if(ignoreDefaultValue && isPeriodAction) {
546
606
  rest.isPeriodAction = true;
547
607
  }
548
608
  return <View testID={testID+"_FilterContainer"} {...containerProps} style={StyleSheet.flatten([theme.styles.w100,containerProps.style])}>
@@ -4,7 +4,7 @@ import {classExtends,isFunction,isNonNullString,defaultStr,defaultObj,defaultVal
4
4
  import {warning,Forms} from "./utils";
5
5
  import Action from "./Action";
6
6
  import View from "$ecomponents/View";
7
- import KeyboardAvoidingView from "./KeyboardAvoidingView";
7
+ import KeyboardAvoidingView from "$ecomponents/KeyboardAvoidingView";
8
8
  import { StyleSheet } from "react-native";
9
9
  import APP from "$capp/instance";
10
10
  import PropTypes from "prop-types";
@@ -8,7 +8,4 @@ export {default as Form} from "./Form";
8
8
 
9
9
  export {default as Action} from "./Action";
10
10
 
11
- export {default as KeyboardAvoidingView} from "./KeyboardAvoidingView";
12
-
13
-
14
11
  export * from "./utils";
@@ -3,15 +3,28 @@ import { FlatList } from 'react-native';
3
3
  import { ScrollView,Dimensions,useWindowDimensions } from 'react-native';
4
4
  import PropTypes from "prop-types";
5
5
  import View from "$ecomponents/View";
6
- import {isMobileNative} from "$cplatform";
6
+ import {isMobileNative,isTouchDevice} from "$cplatform";
7
7
  import {defaultStr,defaultObj} from "$utils";
8
8
  const isNative = isMobileNative();
9
-
9
+ import APP from "$capp/instance";
10
10
  const ScrollViewComponent = React.forwardRef((props,ref) => {
11
11
  const {virtualized,contentProps,mediaQueryUpdateNativeProps,testID:customTestID,children,screenIndent:sIndent,...rest} = props;
12
12
  const testID = defaultStr(customTestID,'RN_ScrollViewComponent');
13
13
  const cProps = defaultObj(contentProps);
14
- const {height} = useWindowDimensions()//:Dimensions.get("window");
14
+ const [layout,setLayout] = React.useState(Dimensions.get("window"));
15
+ const {height} = layout;
16
+ React.useEffect(()=>{
17
+ const onResizePage = ()=>{
18
+ if(isTouchDevice()) return;
19
+ setTimeout(()=>{
20
+ setLayout(Dimensions.get("window"))
21
+ },200);
22
+ }
23
+ APP.on(APP.EVENTS.RESIZE_PAGE,onResizePage);
24
+ return ()=>{
25
+ APP.off(APP.EVENTS.RESIZE_PAGE,onResizePage);
26
+ }
27
+ },[])
15
28
  return virtualized ? <FlatList
16
29
  {...rest}
17
30
  ref = {ref}