@fto-consult/common
Version:
Un ensemble de bibliothèques et d'utilistaires communs pour le développement d'applications javascript
485 lines (432 loc) • 16.2 kB
JavaScript
// Copyright 2022 @fto-consult/Boris Fouomene. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
import React from "react";
import PropTypes from "prop-types";
import isDOMElement from "$cutils/dom/isDOMElement";
import defaultStr from "$cutils/defaultStr";
import uniqid from "$cutils/uniqid"
import {isPlainObj,isNonNullString,isObj,isUndefined,debounce} from "$cutils";
import Component,{ObservableComponent} from "./Component";
import useIsMounted from "./useIsMounted";
import setRef from "./setRef";
import isValidElement from "./isValidElement";
import Dimensions from '$cdimensions';
import {addEventListener,addListener} from "./eventListener";
import useForceRender from "./useForceRender";
import * as isComponents from "./isComponent";
import useStableMemo from "./useStableMemo";
import {isClientSide} from "$cplatform";
import memoize from "./memoize";
import isEquals from "../compare";
import usePrevious,{usePreviousDifferent} from "./usePrevious";
import getTextContent from "./getTextContent";
import useEventCallback from "./useEventCallback";
import useMediaQuery from "./useMediaQuery";
export {useEventCallback};
export {useMediaQuery};
React.useMediaQuery = useMediaQuery;
React.getTextContent = getTextContent;
React.usePrevious = usePrevious;
React.usePreviousDifferent = usePreviousDifferent;
React.useEventCallback = useEventCallback;
React.isEquals = React.areEquals = React.compare = isEquals;
export {usePrevious};
export {isEquals};
export {memoize};
export {useStableMemo};
React.useStableMemo = useStableMemo;
export * from "./isComponent";
for(let i in isComponents){
if(!React[i]){
React[i] = isComponents[i];
}
}
React.useForceRender = useForceRender;
export {useForceRender};
export * from "./eventListener";
React.addEventListener = addEventListener;
React.addListener = addListener;
export {useIsMounted};
React.useIsMounted = useIsMounted;
React.setRef = setRef;
export {isValidElement};
export {Component}
export {setRef};
export {BaseComponent} from "./Component";
export * from "./Component";
export * from "react";
const hasChildren = child => Boolean(child && child.props && child.props.children);
const hasRecursiveChildren = child => hasChildren(child) && typeof child.props.children === 'object';
const isFunction = x => typeof x =="function";
const defaultArray = x => Array.isArray(x) ? x : [];
export const stopEventPropagation = function (e){
if(isPlainObj(e) && React.isEvent(e.event)){
e = e.event;
}
if(e && typeof e =="object" && e.preventDefault){
e.preventDefault();
if(typeof e.stopPropagation =="function") e.stopPropagation();
e.defaultPrevented = true;
if(e.nativeEvent && typeof e.nativeEvent.stopImmediatePropagation =="function"){
e.nativeEvent.stopImmediatePropagation();
}
return true;
}
return false;
}
/****
* retourne l'ensemble des clé pouvant identifier un élément react à partir de la données data, la ou les clés peuvent être spécifiées dans un tableau où une
* chaine de caractère qui sera splité (séparteur virgule);
* il est possible de spécifier plusieurs clés dans une chaine de caractère spéparées de la virgule, surtout si pour la donnée data, on désire
* cumuler plusieurs valeurs de colonnes et les concatener avec le champ concaténateur,
* @param data {object}, l'objet à partir duquel on désire récupérer la clé react
* @param rowKey {string|array}, l'ensemble des clés (chaine de caractères séparés par la virgule ou tableau de chaine de caractère) dont les valeurs associées à l'objet data seront utilisées pour déterminer la clé finale
* @param concatSeparator{string}, la chaine de caractère utiliséee pour concatener les différentes valeurs, lorsque l'objet admet plusieurs éléments constitunt la clé
@return {string}, la clé déterminée à partir de l'objet data
*/
export const getKeyFromObj = (data,rowKey,concatSeparator)=>{
concatSeparator = defaultStr(concatSeparator,"/");
const rKeys = isNonNullString(rowKey)? rowKey.trim().split(",") : rowKey;
if(isObj(data) && Array.isArray(rKeys) && rKeys.length){
let rkVal = "";
rKeys.map((key)=>{
if(!isNonNullString(key)) return;
const v = data[key];
const vv = defaultStr(v ===undefined|| v===null || typeof v=="boolean" || typeof v=='function' || typeof v ==='object' ? "": v?.toString());
if(vv){
rkVal+=(rkVal?concatSeparator:"")+vv;
}
});
if(rkVal){
return rkVal;
}
}
return "";
}
export const getKey = (data,index,rowKey,concatSeparator)=>{
if(!isObj(data) && isObj(index)){
let t = isNonNullString(data)?data : undefined;
data = index;
index = t;
}
if(isObj(data)){
const rVal = getKeyFromObj(data,index,concatSeparator) || getKeyFromObj(data,rowKey,concatSeparator);
if(rVal){
return rVal;
}
let suffix = isNonNullString(data.dbId)? ("-"+data.dbId):"";
if(isNonNullString(data.rowKey)){
suffix+=data.rowKey;
}
if(isNonNullString(data.key)){
return data.key+suffix;
} else if(isNonNullString(data.code)){
return data.code+suffix;
}
}
if(isNonNullString(index) || typeof(index) ==="number"){
return index+"";
}
return uniqid("key-index-"+uniqid("items-idkml"))
}
const filter = (children, filterFn) => {
return Children
.toArray(children)
.filter(filterFn);
};
export const deepForEach = (children, deepForEachFn) => {
//if(!Array.isArray(children)) return React;
React.Children.forEach(children,(child,index) => {
if (hasRecursiveChildren(child)) {
// Each inside the child that has children
deepForEach(child.props.children, deepForEachFn);
}
deepForEachFn(child);
});
return React;
};
export const onlyText = (children) => {
return React.Children
.toArray(children)
.reduce((flattened, child) => [
...flattened,
hasChildren(child) ? onlyText(child.props.children) : child,
], [])
.join('');
};
/*** recherche re */
export const deepFind = (children, deepFindFn) => {
return children
.toArray(children)
.find((child) => {
if (hasRecursiveChildren(child)) {
// Find inside the child that has children
return deepFind(child.props.children, deepFindFn);
}
return deepFindFn(child);
});
};
export const concat = function(){
var args = Array.prototype.slice.call(arguments,0);
var jsx = <React.Fragment/>
for(var i in args){
jsx = <React.Fragment>{jsx}{args[i]}</React.Fragment>
}
return jsx;
}
export const setProps = (ComponentClass,props,result,force) =>{
if(!isObj(result)) result = {};
if(!ComponentClass || !isObj(props)) return result;
let propTypes = {};
if(isObj(ComponentClass.propTypes)){
propTypes = ComponentClass.propTypes;
} else if(ComponentClass.constructor && ComponentClass.constructor.propTypes){
propTypes = ComponentClass.constructor.propTypes;
}
if(!isObj(propTypes)) return force? (Object.size(result,true)>0? result : {...props}) : result;
for(let t in propTypes){
if(props.hasOwnProperty(t) && isUndefined(result[t])){
result[t] = !isUndefined(props[t])? props[t] : result[t];
}
}
return result;
}
export const extractPropTypes = (propTypes) => {
let output = {};
if(!propTypes) return {};
// copy original PropTypes to AxePropTypes
Object.keys(ropTypes.PropTypes).forEach(function (propTypeName) {
if (propTypeName === 'PropTypes') return false;
output[propTypeName] = PropTypes[propTypeName];
});
return output;
}
/**
* useDidUpdate hook
*
* Fires a callback on component update
* Can take in a list of conditions to fire callback when one of the
* conditions changes
*
* @param {Function} callback The callback to be called on update
* @param {Array} conditions The list of variables which trigger update when they are changed
* @returns {undefined}
*/
export const useDidUpdate = React.useDidUpdate = function useDidUpdate(callback, conditions){
const hasMountedRef = React.useRef(false);
if (typeof conditions !== 'undefined' && !Array.isArray(conditions)) {
conditions = [conditions];
} else if (Array.isArray(conditions) && conditions.length === 0) {
console.warn(
'Using [] as the second argument makes useDidUpdate a noop. The second argument should either be `undefined` or an array of length greater than 0.'
);
}
React.useEffect(() => {
if (hasMountedRef.current) {
callback();
} else {
hasMountedRef.current = true;
}
}, conditions);
}
/**
* useDidMount hook
* Calls a function on mount
*
* @param {Function} callback Callback function to be called on mount
*/
export const useDidMount = React.useDidMount = function useDidMount(callback) {
React.useEffect(() => {
if (typeof callback === 'function') {
callback();
}
}, []);
}
export const useOnRenderTimeout = React.useOnRenderTimeout = 500;
/**
* useOnRender hook
* Calls a function on every render
*
* @param {Function} callback Callback function to be called on mount
*/
export const useOnRender = React.useOnRender = function useOnRender(callback,timeout) {
React.useEffect(() => {
callback = typeof callback =='function'? callback : x=>true;
setTimeout(callback,typeof timeout =='number'? timeout : React.useOnRenderTimeout);
});
}
/**
* useWillUnmount hook
* Fires a callback just before component unmounts
*
* @param {Function} callback Callback to be called before unmount
*/
export const useWillUnmount = React.useWillUnmount = function useWillUnmount(callback) {
// run only once
useEffect(() => {
return callback;
}, []);
}
export const useStateIfMounted = React.useStateIfMounted = function(initialValue){
const isMounted = useIsMounted();
const [state, setState] = React.useState(initialValue);
function newSetState(value) {
if (isMounted()) {
setState(value);
}
}
return [state, newSetState]
}
export default React;
export const isSyntheticEvent = React.isSyntheticEvent = (event)=> {
if(!event || typeof event !=='object' || !event.constructor || !isNonNullString(event.constructor.name)) return false;
return event.constructor.name.startsWith('Synthetic') && event.constructor.name.endsWith('Event') ? true : false;
}
export const isEvent = React.isEvent = isSyntheticEvent;
export const AFTER_INTERACTIONS_TIMEOUT = 10;
export function useLatest(value) {
const ref = React.useRef(value)
ref.current = value
return ref
}
React.useLatest = useLatest;
export const useEffectAsync = React.useEffectAsync = (operation,deps) => {
React.useEffect(() => {
operation().then()
}, deps)}
React.isComponent = React.isComponent || React.isValidElement;///check if is valid react component
if(isClientSide()){
React.Children.deepForEach = deepForEach;
React.Children.exists = React.Children.has = hasChildren;
React.Children.onlyText = onlyText;
React.Children.text = onlyText;
React.Children.filter = filter;
React.Children.deepFind = deepFind;
}
if(!React.stopEventPropagation){
Object.defineProperties(React,{
isValidElement : {
value : isValidElement
},
deepFind : {value :deepFind},
concat : {value:concat},
extractPropTypes : {value:extractPropTypes},
hasChildren : {value:hasChildren},
childrenExists : {value:hasRecursiveChildren},
stopEventPropagation : {
value : stopEventPropagation,override : false,writable : false
},
key : {
value : getKey,override:false,writable:false
},
getKey : {
value : getKey,
},
getKeyFromObj : {
value : getKeyFromObj,
},
setProps : {
value : setProps,override:false,writable:false
},
AppComponent : {
value : Component,
},
ObservableComponent : {value:ObservableComponent},
})
}
//default base component for each element
export const mergeRefs = React.mergeRefs = (...args)=>{
return function forwardRef(node) {
args.forEach((ref) => {
if (ref == null || !ref) {
return;
}
if (typeof ref === 'function') {
ref(node);
return;
}
if (typeof ref === 'object') {
ref.current = node;
return;
}
console.error(
`mergeRefs cannot handle Refs of type boolean, number or string, received ref ${String(
ref
)}`
);
});
};
}
export const useMergeRefs = React.useMergeRefs = (...args) =>{
return React.useMemo(() => mergeRefs(...args),[...args]);
}
export const useLazyRef = React.useLazyRef = function useLazyRef(callback) {
const lazyRef = React.useRef();
if (lazyRef.current === undefined) {
lazyRef.current = callback();
}
return lazyRef;
}
export const useAnimatedValue = React.useAnimatedValue = function useAnimatedValue(initialValue) {
//const { current } = useLazyRef(() => new Animated.Value(initialValue));
//return current;
}
/***** permett de récupérer les arguments onPress, utile pour appeler la fonction onPress des menus */
export const getOnPressArgs = React.getOnPressArgs = (args)=>{
let event = undefined,rest = {};
if(React.isEvent(args)){
event = args;
} else if(isPlainObj(args)){
rest = args;
event = React.isEvent(rest.event);
}
React.stopEventPropagation(event);
return {...rest,event};
}
export const withHooks = React.withHooks = (Component,hooks) => {
return ({props}) => {
let hProps = {...props}
const callCB = (hook)=>{
if(typeof hook ==='function'){
const hR = hook(hProps);
if(isObj(hR)){
hProps = {...hProps,...hR};
}
return hR;
}
return false;
}
if(isArray(hooks)){
hooks.map((cb)=>{
callCB(cb);
})
} else if(typeof hooks =='function'){
callCB(hooks);
}
return <Component {...hProps} />;
};
};
export const useSwipe = React. useSwipe = function useSwipe({onSwipeLeft, onSwipeRight, rangeOffset = 4}) {
let firstTouch = 0
// set user touch start position
function onTouchStart(e) {
firstTouch = e.nativeEvent.pageX
}
// when touch ends check for swipe directions
function onTouchEnd(e){
const windowWidth = Dimensions.get("window").width;
// get touch position and screen size
const positionX = e.nativeEvent.pageX
const range = windowWidth / rangeOffset
// check if position is growing positively and has reached specified range
if(positionX - firstTouch > range){
onSwipeRight && onSwipeRight({range,event:e})
}
// check if position is growing negatively and has reached specified range
else if(firstTouch - positionX > range){
onSwipeLeft && onSwipeLeft({range,event:e})
}
}
return {onTouchStart, onTouchEnd};
}