@etsoo/react 1.5.25 → 1.5.28
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/lib/app/ReactApp.js +6 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/mu/InputField.js +6 -8
- package/lib/mu/SearchBar.js +16 -29
- package/lib/mu/SearchField.js +6 -8
- package/lib/mu/TextFieldEx.js +6 -8
- package/lib/mu/Tiplist.js +5 -12
- package/lib/mu/TooltipClick.js +6 -12
- package/lib/uses/useDelayedExecutor.d.ts +5 -0
- package/lib/uses/useDelayedExecutor.js +11 -0
- package/lib/uses/useDimensions.d.ts +0 -1
- package/lib/uses/useDimensions.js +38 -43
- package/lib/uses/useTimeout.d.ts +1 -1
- package/lib/uses/useTimeout.js +6 -14
- package/package.json +5 -5
- package/src/app/ReactApp.ts +11 -0
- package/src/index.ts +1 -0
- package/src/mu/InputField.tsx +7 -6
- package/src/mu/SearchBar.tsx +19 -34
- package/src/mu/SearchField.tsx +7 -6
- package/src/mu/TextFieldEx.tsx +7 -6
- package/src/mu/Tiplist.tsx +6 -20
- package/src/mu/TooltipClick.tsx +8 -17
- package/src/uses/useDelayedExecutor.ts +15 -0
- package/src/uses/useDimensions.ts +47 -55
- package/src/uses/useTimeout.ts +11 -17
package/lib/app/ReactApp.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ActionResultError, BridgeUtils, CoreApp, createClient } from '@etsoo/appscript';
|
|
2
|
+
import { NotificationMessageType } from '@etsoo/notificationbase';
|
|
2
3
|
import { WindowStorage } from '@etsoo/shared';
|
|
3
4
|
import React from 'react';
|
|
4
5
|
import { NotifierMU } from '../mu/NotifierMU';
|
|
@@ -68,6 +69,11 @@ export class ReactApp extends CoreApp {
|
|
|
68
69
|
BridgeUtils.host == null
|
|
69
70
|
? undefined
|
|
70
71
|
: ReactUtils.getMemoryHistory(BridgeUtils.host.getStartUrl());
|
|
72
|
+
if (BridgeUtils.host) {
|
|
73
|
+
BridgeUtils.host.onUpdate((app, version) => {
|
|
74
|
+
this.notifier.message(NotificationMessageType.Success, this.get('updateTip') + `(${[app, version].join(', ')})`, this.get('updateReady'));
|
|
75
|
+
});
|
|
76
|
+
}
|
|
71
77
|
this.cultureState = new CultureState(settings.currentCulture);
|
|
72
78
|
this.pageState = new PageState();
|
|
73
79
|
globalApp = this;
|
package/lib/index.d.ts
CHANGED
|
@@ -88,6 +88,7 @@ export * from './states/RouterProps';
|
|
|
88
88
|
export * from './states/State';
|
|
89
89
|
export * from './states/UserState';
|
|
90
90
|
export * from './uses/useCombinedRefs';
|
|
91
|
+
export * from './uses/useDelayedExecutor';
|
|
91
92
|
export * from './uses/useDimensions';
|
|
92
93
|
export * from './uses/useTimeout';
|
|
93
94
|
export * from './uses/useWindowSize';
|
package/lib/index.js
CHANGED
|
@@ -94,6 +94,7 @@ export * from './states/State';
|
|
|
94
94
|
export * from './states/UserState';
|
|
95
95
|
// uses
|
|
96
96
|
export * from './uses/useCombinedRefs';
|
|
97
|
+
export * from './uses/useDelayedExecutor';
|
|
97
98
|
export * from './uses/useDimensions';
|
|
98
99
|
export * from './uses/useTimeout';
|
|
99
100
|
export * from './uses/useWindowSize';
|
package/lib/mu/InputField.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { TextField } from '@mui/material';
|
|
2
2
|
import React from 'react';
|
|
3
|
+
import { useDelayedExecutor } from '../uses/useDelayedExecutor';
|
|
3
4
|
import { MUGlobal } from './MUGlobal';
|
|
4
5
|
/**
|
|
5
6
|
* Input field
|
|
@@ -15,7 +16,9 @@ export function InputField(props) {
|
|
|
15
16
|
if (readOnly != null)
|
|
16
17
|
InputProps.readOnly = readOnly;
|
|
17
18
|
const isMounted = React.useRef(true);
|
|
18
|
-
const
|
|
19
|
+
const delayed = onChange != null && changeDelay != null && changeDelay >= 1
|
|
20
|
+
? useDelayedExecutor(onChange, changeDelay)
|
|
21
|
+
: undefined;
|
|
19
22
|
const onChangeEx = (event) => {
|
|
20
23
|
if (onChange == null)
|
|
21
24
|
return;
|
|
@@ -23,17 +26,12 @@ export function InputField(props) {
|
|
|
23
26
|
onChange(event);
|
|
24
27
|
return;
|
|
25
28
|
}
|
|
26
|
-
|
|
27
|
-
window.clearTimeout(delaySeed.current);
|
|
28
|
-
delaySeed.current = window.setTimeout(() => {
|
|
29
|
-
if (isMounted.current)
|
|
30
|
-
onChange(event);
|
|
31
|
-
}, changeDelay);
|
|
29
|
+
delayed === null || delayed === void 0 ? void 0 : delayed.call(undefined, event);
|
|
32
30
|
};
|
|
33
31
|
React.useEffect(() => {
|
|
34
32
|
return () => {
|
|
35
33
|
isMounted.current = false;
|
|
36
|
-
|
|
34
|
+
delayed === null || delayed === void 0 ? void 0 : delayed.clear();
|
|
37
35
|
};
|
|
38
36
|
}, []);
|
|
39
37
|
// Layout
|
package/lib/mu/SearchBar.js
CHANGED
|
@@ -5,6 +5,7 @@ import { useDimensions } from '../uses/useDimensions';
|
|
|
5
5
|
import { DomUtils } from '@etsoo/shared';
|
|
6
6
|
import { Labels } from '../app/Labels';
|
|
7
7
|
import { ReactUtils } from '../app/ReactUtils';
|
|
8
|
+
import { useDelayedExecutor } from '..';
|
|
8
9
|
// Cached width attribute name
|
|
9
10
|
const cachedWidthName = 'data-cached-width';
|
|
10
11
|
// Reset form
|
|
@@ -179,7 +180,7 @@ export function SearchBar(props) {
|
|
|
179
180
|
return;
|
|
180
181
|
if (state.form == null)
|
|
181
182
|
state.form = event.currentTarget;
|
|
182
|
-
|
|
183
|
+
delayed.call();
|
|
183
184
|
};
|
|
184
185
|
// Handle more button click
|
|
185
186
|
const handleMore = () => {
|
|
@@ -191,8 +192,18 @@ export function SearchBar(props) {
|
|
|
191
192
|
return;
|
|
192
193
|
if (state.moreForm == null)
|
|
193
194
|
state.moreForm = event.currentTarget;
|
|
194
|
-
|
|
195
|
+
delayed.call();
|
|
195
196
|
};
|
|
197
|
+
// Submit at once
|
|
198
|
+
const handleSubmitInstant = (reset = false) => {
|
|
199
|
+
// Prepare data
|
|
200
|
+
const data = new FormData(state.form);
|
|
201
|
+
if (state.moreForm != null) {
|
|
202
|
+
DomUtils.mergeFormData(data, new FormData(state.moreForm));
|
|
203
|
+
}
|
|
204
|
+
onSubmit(data, reset);
|
|
205
|
+
};
|
|
206
|
+
const delayed = useDelayedExecutor(handleSubmitInstant, 480);
|
|
196
207
|
// Reset
|
|
197
208
|
const handleReset = () => {
|
|
198
209
|
// Clear form values
|
|
@@ -203,36 +214,12 @@ export function SearchBar(props) {
|
|
|
203
214
|
// Resubmit
|
|
204
215
|
handleSubmitInstant(true);
|
|
205
216
|
};
|
|
206
|
-
// Submit
|
|
207
|
-
const handleSubmit = (delay = 480) => {
|
|
208
|
-
if (state.submitSeed != null) {
|
|
209
|
-
clearTimeout(state.submitSeed);
|
|
210
|
-
}
|
|
211
|
-
// Delay the change
|
|
212
|
-
state.submitSeed = window.setTimeout(handleSubmitInstant, delay);
|
|
213
|
-
};
|
|
214
|
-
// Submit at once
|
|
215
|
-
const handleSubmitInstant = (reset = false) => {
|
|
216
|
-
// Reset timeout
|
|
217
|
-
state.submitSeed = undefined;
|
|
218
|
-
// Prepare data
|
|
219
|
-
const data = new FormData(state.form);
|
|
220
|
-
if (state.moreForm != null) {
|
|
221
|
-
DomUtils.mergeFormData(data, new FormData(state.moreForm));
|
|
222
|
-
}
|
|
223
|
-
onSubmit(data, reset);
|
|
224
|
-
};
|
|
225
|
-
// First loading
|
|
226
217
|
React.useEffect(() => {
|
|
218
|
+
// Delayed way
|
|
219
|
+
delayed.call(100);
|
|
227
220
|
return () => {
|
|
228
|
-
|
|
229
|
-
if (state.submitSeed != null)
|
|
230
|
-
clearTimeout(state.submitSeed);
|
|
221
|
+
delayed.clear();
|
|
231
222
|
};
|
|
232
|
-
}, []);
|
|
233
|
-
React.useEffect(() => {
|
|
234
|
-
// Delayed way
|
|
235
|
-
handleSubmit(100);
|
|
236
223
|
}, [className]);
|
|
237
224
|
// Layout
|
|
238
225
|
return (React.createElement(React.Fragment, null,
|
package/lib/mu/SearchField.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { TextField } from '@mui/material';
|
|
2
2
|
import React from 'react';
|
|
3
|
+
import { useDelayedExecutor } from '../uses/useDelayedExecutor';
|
|
3
4
|
import { MUGlobal } from './MUGlobal';
|
|
4
5
|
/**
|
|
5
6
|
* Search field
|
|
@@ -15,7 +16,9 @@ export function SearchField(props) {
|
|
|
15
16
|
if (readOnly != null)
|
|
16
17
|
InputProps.readOnly = readOnly;
|
|
17
18
|
const isMounted = React.useRef(true);
|
|
18
|
-
const
|
|
19
|
+
const delayed = onChange != null && changeDelay != null && changeDelay >= 1
|
|
20
|
+
? useDelayedExecutor(onChange, changeDelay)
|
|
21
|
+
: undefined;
|
|
19
22
|
const onChangeEx = (event) => {
|
|
20
23
|
if (onChange == null)
|
|
21
24
|
return;
|
|
@@ -23,17 +26,12 @@ export function SearchField(props) {
|
|
|
23
26
|
onChange(event);
|
|
24
27
|
return;
|
|
25
28
|
}
|
|
26
|
-
|
|
27
|
-
window.clearTimeout(delaySeed.current);
|
|
28
|
-
delaySeed.current = window.setTimeout(() => {
|
|
29
|
-
if (isMounted.current)
|
|
30
|
-
onChange(event);
|
|
31
|
-
}, changeDelay);
|
|
29
|
+
delayed === null || delayed === void 0 ? void 0 : delayed.call(undefined, event);
|
|
32
30
|
};
|
|
33
31
|
React.useEffect(() => {
|
|
34
32
|
return () => {
|
|
35
33
|
isMounted.current = false;
|
|
36
|
-
|
|
34
|
+
delayed === null || delayed === void 0 ? void 0 : delayed.clear();
|
|
37
35
|
};
|
|
38
36
|
}, []);
|
|
39
37
|
// Layout
|
package/lib/mu/TextFieldEx.js
CHANGED
|
@@ -4,6 +4,7 @@ import { MUGlobal } from './MUGlobal';
|
|
|
4
4
|
import { Clear, Visibility } from '@mui/icons-material';
|
|
5
5
|
import useCombinedRefs from '../uses/useCombinedRefs';
|
|
6
6
|
import { Keyboard } from '@etsoo/shared';
|
|
7
|
+
import { useDelayedExecutor } from '../uses/useDelayedExecutor';
|
|
7
8
|
export const TextFieldEx = React.forwardRef((props, ref) => {
|
|
8
9
|
// Destructure
|
|
9
10
|
const { changeDelay, error, fullWidth = true, helperText, InputProps = {}, onChange, onKeyPress, onEnter, inputRef, readOnly, showClear, showPassword, type, variant = MUGlobal.textFieldVariant, ...rest } = props;
|
|
@@ -91,7 +92,9 @@ export const TextFieldEx = React.forwardRef((props, ref) => {
|
|
|
91
92
|
}
|
|
92
93
|
}), []);
|
|
93
94
|
const isMounted = React.useRef(true);
|
|
94
|
-
const
|
|
95
|
+
const delayed = onChange != null && changeDelay != null && changeDelay >= 1
|
|
96
|
+
? useDelayedExecutor(onChange, changeDelay)
|
|
97
|
+
: undefined;
|
|
95
98
|
const onChangeEx = (event) => {
|
|
96
99
|
if (errorText != null) {
|
|
97
100
|
// Reset
|
|
@@ -111,17 +114,12 @@ export const TextFieldEx = React.forwardRef((props, ref) => {
|
|
|
111
114
|
onChange(event);
|
|
112
115
|
return;
|
|
113
116
|
}
|
|
114
|
-
|
|
115
|
-
window.clearTimeout(delaySeed.current);
|
|
116
|
-
delaySeed.current = window.setTimeout(() => {
|
|
117
|
-
if (isMounted.current)
|
|
118
|
-
onChange(event);
|
|
119
|
-
}, changeDelay);
|
|
117
|
+
delayed === null || delayed === void 0 ? void 0 : delayed.call(undefined, event);
|
|
120
118
|
};
|
|
121
119
|
React.useEffect(() => {
|
|
122
120
|
return () => {
|
|
123
121
|
isMounted.current = false;
|
|
124
|
-
|
|
122
|
+
delayed === null || delayed === void 0 ? void 0 : delayed.clear();
|
|
125
123
|
};
|
|
126
124
|
}, []);
|
|
127
125
|
// Textfield
|
package/lib/mu/Tiplist.js
CHANGED
|
@@ -2,6 +2,7 @@ import { DataTypes } from '@etsoo/shared';
|
|
|
2
2
|
import { Autocomplete } from '@mui/material';
|
|
3
3
|
import React from 'react';
|
|
4
4
|
import { ReactUtils } from '../app/ReactUtils';
|
|
5
|
+
import { useDelayedExecutor } from '../uses/useDelayedExecutor';
|
|
5
6
|
import { InputField } from './InputField';
|
|
6
7
|
import { SearchField } from './SearchField';
|
|
7
8
|
/**
|
|
@@ -57,17 +58,11 @@ export function Tiplist(props) {
|
|
|
57
58
|
}
|
|
58
59
|
// Stop bubble
|
|
59
60
|
event.stopPropagation();
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
clearTimeout(state.loadDataSeed);
|
|
63
|
-
}
|
|
64
|
-
// Delay 480 for loading data
|
|
65
|
-
state.loadDataSeed = window.setTimeout(loadDataDirect, 480, event.currentTarget.value);
|
|
61
|
+
// Call with delay
|
|
62
|
+
delayed.call(undefined, event.currentTarget.value);
|
|
66
63
|
};
|
|
67
64
|
// Directly load data
|
|
68
65
|
const loadDataDirect = (keyword, id) => {
|
|
69
|
-
// Reset seed value
|
|
70
|
-
state.loadDataSeed = undefined;
|
|
71
66
|
// Reset options
|
|
72
67
|
// setOptions([]);
|
|
73
68
|
if (id == null) {
|
|
@@ -96,6 +91,7 @@ export function Tiplist(props) {
|
|
|
96
91
|
});
|
|
97
92
|
});
|
|
98
93
|
};
|
|
94
|
+
const delayed = useDelayedExecutor(loadDataDirect, 480);
|
|
99
95
|
const setInputValue = (value) => {
|
|
100
96
|
var _a;
|
|
101
97
|
stateUpdate({ value });
|
|
@@ -126,11 +122,8 @@ export function Tiplist(props) {
|
|
|
126
122
|
}
|
|
127
123
|
React.useEffect(() => {
|
|
128
124
|
return () => {
|
|
129
|
-
// Clear any seed
|
|
130
|
-
if (state.loadDataSeed != null) {
|
|
131
|
-
clearTimeout(state.loadDataSeed);
|
|
132
|
-
}
|
|
133
125
|
isMounted.current = false;
|
|
126
|
+
delayed.clear();
|
|
134
127
|
};
|
|
135
128
|
}, []);
|
|
136
129
|
// Layout
|
package/lib/mu/TooltipClick.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ClickAwayListener, Tooltip } from '@mui/material';
|
|
2
2
|
import React from 'react';
|
|
3
|
+
import { useDelayedExecutor } from '../uses/useDelayedExecutor';
|
|
3
4
|
/**
|
|
4
5
|
* Tooltip with click visibility
|
|
5
6
|
* @param props Props
|
|
@@ -12,26 +13,19 @@ export function TooltipClick(props) {
|
|
|
12
13
|
// State
|
|
13
14
|
const [localTitle, setTitle] = React.useState(title);
|
|
14
15
|
const [open, setOpen] = React.useState(false);
|
|
15
|
-
const
|
|
16
|
+
const delayed = leaveDelay > 0
|
|
17
|
+
? useDelayedExecutor(() => setOpen(false), leaveDelay)
|
|
18
|
+
: undefined;
|
|
16
19
|
// Callback for open the tooltip
|
|
17
20
|
const openTooltip = (newTitle) => {
|
|
18
21
|
setOpen(true);
|
|
19
22
|
if (newTitle)
|
|
20
23
|
setTitle(newTitle);
|
|
21
|
-
|
|
22
|
-
clearLeaveSeed();
|
|
23
|
-
leaveSeed.current = window.setTimeout(() => setOpen(false), leaveDelay);
|
|
24
|
-
}
|
|
25
|
-
};
|
|
26
|
-
const clearLeaveSeed = () => {
|
|
27
|
-
if (leaveSeed.current > 0) {
|
|
28
|
-
window.clearTimeout(leaveSeed.current);
|
|
29
|
-
leaveSeed.current = 0;
|
|
30
|
-
}
|
|
24
|
+
delayed === null || delayed === void 0 ? void 0 : delayed.call();
|
|
31
25
|
};
|
|
32
26
|
React.useEffect(() => {
|
|
33
27
|
return () => {
|
|
34
|
-
|
|
28
|
+
delayed === null || delayed === void 0 ? void 0 : delayed.clear();
|
|
35
29
|
};
|
|
36
30
|
}, []);
|
|
37
31
|
// Layout
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ExtendUtils } from '@etsoo/shared';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* Create delayed executor
|
|
5
|
+
*/
|
|
6
|
+
export function useDelayedExecutor(func, delayMiliseconcs) {
|
|
7
|
+
const ref = React.useRef();
|
|
8
|
+
if (ref.current == null)
|
|
9
|
+
ref.current = ExtendUtils.delayedExecutor(func, delayMiliseconcs);
|
|
10
|
+
return ref.current;
|
|
11
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DomUtils } from '@etsoo/shared';
|
|
2
2
|
import React from 'react';
|
|
3
|
+
import { useDelayedExecutor } from './useDelayedExecutor';
|
|
3
4
|
/**
|
|
4
5
|
* Calculate element(s) dimensions
|
|
5
6
|
* @param elements Observed elments count
|
|
@@ -13,10 +14,42 @@ export function useDimensions(elements, updateCallback, miliseconds = 50, equalC
|
|
|
13
14
|
count: 0,
|
|
14
15
|
indices: []
|
|
15
16
|
});
|
|
17
|
+
// Dimentions
|
|
18
|
+
const dimensions = React.useRef();
|
|
19
|
+
if (dimensions.current == null) {
|
|
20
|
+
// Init
|
|
21
|
+
const init = [];
|
|
22
|
+
for (let e = 0; e < elements; e++) {
|
|
23
|
+
init.push(((index) => {
|
|
24
|
+
return [
|
|
25
|
+
(instance) => {
|
|
26
|
+
if (instance != null) {
|
|
27
|
+
// Current element
|
|
28
|
+
const currentElement = init[index][1];
|
|
29
|
+
if (currentElement != null) {
|
|
30
|
+
// Same target, return
|
|
31
|
+
if (currentElement == instance)
|
|
32
|
+
return;
|
|
33
|
+
// Cancel observation
|
|
34
|
+
resizeObserver.unobserve(currentElement);
|
|
35
|
+
}
|
|
36
|
+
// Update element
|
|
37
|
+
init[index][1] = instance;
|
|
38
|
+
// Start observe
|
|
39
|
+
resizeObserver.observe(instance);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
];
|
|
43
|
+
})(e));
|
|
44
|
+
}
|
|
45
|
+
dimensions.current = init;
|
|
46
|
+
}
|
|
47
|
+
const delayed = useDelayedExecutor(setState, miliseconds);
|
|
16
48
|
// Observer
|
|
17
49
|
const resizeObserver = new ResizeObserver((entries) => {
|
|
18
50
|
// Update Rect
|
|
19
51
|
const indices = [];
|
|
52
|
+
const init = dimensions.current;
|
|
20
53
|
entries.forEach((entry) => {
|
|
21
54
|
const index = init.findIndex((item) => item[1] === entry.target);
|
|
22
55
|
if (index !== -1) {
|
|
@@ -41,57 +74,19 @@ export function useDimensions(elements, updateCallback, miliseconds = 50, equalC
|
|
|
41
74
|
});
|
|
42
75
|
// Update state
|
|
43
76
|
if (indices.length > 0) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
clearTimeout(state.seed);
|
|
48
|
-
}
|
|
49
|
-
state.seed = window.setTimeout((localUpdate) => {
|
|
50
|
-
setState(localUpdate);
|
|
51
|
-
}, miliseconds, update);
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
setState(update);
|
|
55
|
-
}
|
|
77
|
+
// Count only for unique update
|
|
78
|
+
const update = { count: state.count + 1, indices };
|
|
79
|
+
delayed.call(undefined, update);
|
|
56
80
|
}
|
|
57
81
|
});
|
|
58
|
-
// Init
|
|
59
|
-
const init = [];
|
|
60
|
-
for (let e = 0; e < elements; e++) {
|
|
61
|
-
init.push(((index) => {
|
|
62
|
-
return [
|
|
63
|
-
(instance) => {
|
|
64
|
-
if (instance != null) {
|
|
65
|
-
// Current element
|
|
66
|
-
const currentElement = init[index][1];
|
|
67
|
-
if (currentElement != null) {
|
|
68
|
-
// Same target, return
|
|
69
|
-
if (currentElement == instance)
|
|
70
|
-
return;
|
|
71
|
-
// Cancel observation
|
|
72
|
-
resizeObserver.unobserve(currentElement);
|
|
73
|
-
}
|
|
74
|
-
// Update element
|
|
75
|
-
init[index][1] = instance;
|
|
76
|
-
// Start observe
|
|
77
|
-
resizeObserver.observe(instance);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
];
|
|
81
|
-
})(e));
|
|
82
|
-
}
|
|
83
|
-
// Dimensions
|
|
84
|
-
const [dimensions] = React.useState(init);
|
|
85
82
|
// Layout ready
|
|
86
83
|
React.useEffect(() => {
|
|
87
84
|
return () => {
|
|
88
85
|
// Clear the observer
|
|
89
86
|
resizeObserver.disconnect();
|
|
90
|
-
|
|
91
|
-
clearTimeout(state.seed);
|
|
92
|
-
}
|
|
87
|
+
delayed.clear();
|
|
93
88
|
};
|
|
94
89
|
}, []);
|
|
95
90
|
// Return
|
|
96
|
-
return { dimensions, state };
|
|
91
|
+
return { dimensions: dimensions.current, state };
|
|
97
92
|
}
|
package/lib/uses/useTimeout.d.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
* @param action Action function
|
|
4
4
|
* @param milliseconds Interval of milliseconds
|
|
5
5
|
*/
|
|
6
|
-
export declare const useTimeout: (action:
|
|
6
|
+
export declare const useTimeout: (action: (...args: any[]) => void, milliseconds: number) => {
|
|
7
7
|
cancel: () => void;
|
|
8
8
|
};
|
package/lib/uses/useTimeout.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ExtendUtils } from '@etsoo/shared';
|
|
1
2
|
import React from 'react';
|
|
2
3
|
/**
|
|
3
4
|
* For setTimeout to merge actions
|
|
@@ -5,26 +6,17 @@ import React from 'react';
|
|
|
5
6
|
* @param milliseconds Interval of milliseconds
|
|
6
7
|
*/
|
|
7
8
|
export const useTimeout = (action, milliseconds) => {
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
// Cancel function
|
|
11
|
-
const cancel = () => {
|
|
12
|
-
if (seed > 0) {
|
|
13
|
-
clearTimeout(seed);
|
|
14
|
-
seed = 0;
|
|
15
|
-
}
|
|
16
|
-
};
|
|
9
|
+
// Delayed
|
|
10
|
+
const d = ExtendUtils.delayedExecutor(action, milliseconds);
|
|
17
11
|
// Merge into the life cycle
|
|
18
12
|
React.useEffect(() => {
|
|
19
|
-
|
|
20
|
-
action.call(null);
|
|
21
|
-
}, milliseconds);
|
|
13
|
+
d.call();
|
|
22
14
|
return () => {
|
|
23
|
-
|
|
15
|
+
d.clear();
|
|
24
16
|
};
|
|
25
17
|
}, []);
|
|
26
18
|
// Return cancel method
|
|
27
19
|
return {
|
|
28
|
-
cancel
|
|
20
|
+
cancel: d.clear
|
|
29
21
|
};
|
|
30
22
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@etsoo/react",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.28",
|
|
4
4
|
"description": "TypeScript ReactJs framework",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
"@emotion/react": "^11.8.1",
|
|
51
51
|
"@emotion/style": "^0.8.0",
|
|
52
52
|
"@emotion/styled": "^11.8.1",
|
|
53
|
-
"@etsoo/appscript": "^1.2.
|
|
54
|
-
"@etsoo/notificationbase": "^1.1.
|
|
55
|
-
"@etsoo/shared": "^1.1.
|
|
53
|
+
"@etsoo/appscript": "^1.2.57",
|
|
54
|
+
"@etsoo/notificationbase": "^1.1.2",
|
|
55
|
+
"@etsoo/shared": "^1.1.14",
|
|
56
56
|
"@mui/icons-material": "^5.4.2",
|
|
57
57
|
"@mui/material": "^5.4.3",
|
|
58
58
|
"@reach/router": "^1.3.4",
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
"eslint": "^8.10.0",
|
|
89
89
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
90
90
|
"eslint-plugin-import": "^2.25.4",
|
|
91
|
-
"eslint-plugin-react": "^7.29.
|
|
91
|
+
"eslint-plugin-react": "^7.29.2",
|
|
92
92
|
"jest": "^27.5.1",
|
|
93
93
|
"react-test-renderer": "^17.0.2",
|
|
94
94
|
"ts-jest": "^27.1.3",
|
package/src/app/ReactApp.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
IUserData
|
|
11
11
|
} from '@etsoo/appscript';
|
|
12
12
|
import {
|
|
13
|
+
NotificationMessageType,
|
|
13
14
|
NotificationRenderProps,
|
|
14
15
|
NotificationReturn
|
|
15
16
|
} from '@etsoo/notificationbase';
|
|
@@ -225,6 +226,16 @@ export class ReactApp<
|
|
|
225
226
|
? undefined
|
|
226
227
|
: ReactUtils.getMemoryHistory(BridgeUtils.host.getStartUrl());
|
|
227
228
|
|
|
229
|
+
if (BridgeUtils.host) {
|
|
230
|
+
BridgeUtils.host.onUpdate((app, version) => {
|
|
231
|
+
this.notifier.message(
|
|
232
|
+
NotificationMessageType.Success,
|
|
233
|
+
this.get('updateTip') + `(${[app, version].join(', ')})`,
|
|
234
|
+
this.get('updateReady')
|
|
235
|
+
);
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
228
239
|
this.cultureState = new CultureState(settings.currentCulture);
|
|
229
240
|
this.pageState = new PageState<P>();
|
|
230
241
|
|
package/src/index.ts
CHANGED
|
@@ -101,6 +101,7 @@ export * from './states/UserState';
|
|
|
101
101
|
|
|
102
102
|
// uses
|
|
103
103
|
export * from './uses/useCombinedRefs';
|
|
104
|
+
export * from './uses/useDelayedExecutor';
|
|
104
105
|
export * from './uses/useDimensions';
|
|
105
106
|
export * from './uses/useTimeout';
|
|
106
107
|
export * from './uses/useWindowSize';
|
package/src/mu/InputField.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { TextField, TextFieldProps } from '@mui/material';
|
|
2
2
|
import React from 'react';
|
|
3
|
+
import { useDelayedExecutor } from '../uses/useDelayedExecutor';
|
|
3
4
|
import { MUGlobal } from './MUGlobal';
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -42,7 +43,10 @@ export function InputField(props: InputFieldProps) {
|
|
|
42
43
|
if (readOnly != null) InputProps.readOnly = readOnly;
|
|
43
44
|
|
|
44
45
|
const isMounted = React.useRef(true);
|
|
45
|
-
const
|
|
46
|
+
const delayed =
|
|
47
|
+
onChange != null && changeDelay != null && changeDelay >= 1
|
|
48
|
+
? useDelayedExecutor(onChange, changeDelay)
|
|
49
|
+
: undefined;
|
|
46
50
|
|
|
47
51
|
const onChangeEx = (
|
|
48
52
|
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
|
|
@@ -54,16 +58,13 @@ export function InputField(props: InputFieldProps) {
|
|
|
54
58
|
return;
|
|
55
59
|
}
|
|
56
60
|
|
|
57
|
-
|
|
58
|
-
delaySeed.current = window.setTimeout(() => {
|
|
59
|
-
if (isMounted.current) onChange(event);
|
|
60
|
-
}, changeDelay);
|
|
61
|
+
delayed?.call(undefined, event);
|
|
61
62
|
};
|
|
62
63
|
|
|
63
64
|
React.useEffect(() => {
|
|
64
65
|
return () => {
|
|
65
66
|
isMounted.current = false;
|
|
66
|
-
|
|
67
|
+
delayed?.clear();
|
|
67
68
|
};
|
|
68
69
|
}, []);
|
|
69
70
|
|
package/src/mu/SearchBar.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import { useDimensions } from '../uses/useDimensions';
|
|
|
5
5
|
import { DomUtils } from '@etsoo/shared';
|
|
6
6
|
import { Labels } from '../app/Labels';
|
|
7
7
|
import { ReactUtils } from '../app/ReactUtils';
|
|
8
|
+
import { useDelayedExecutor } from '..';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Search bar props
|
|
@@ -103,7 +104,6 @@ export function SearchBar(props: SearchBarProps) {
|
|
|
103
104
|
const state = React.useRef<{
|
|
104
105
|
form?: HTMLFormElement;
|
|
105
106
|
moreForm?: HTMLFormElement;
|
|
106
|
-
submitSeed?: number;
|
|
107
107
|
lastMaxWidth: number;
|
|
108
108
|
hasMore: boolean;
|
|
109
109
|
}>({ hasMore: true, lastMaxWidth: 9999 }).current;
|
|
@@ -256,7 +256,7 @@ export function SearchBar(props: SearchBarProps) {
|
|
|
256
256
|
|
|
257
257
|
if (state.form == null) state.form = event.currentTarget;
|
|
258
258
|
|
|
259
|
-
|
|
259
|
+
delayed.call();
|
|
260
260
|
};
|
|
261
261
|
|
|
262
262
|
// Handle more button click
|
|
@@ -270,34 +270,11 @@ export function SearchBar(props: SearchBarProps) {
|
|
|
270
270
|
|
|
271
271
|
if (state.moreForm == null) state.moreForm = event.currentTarget;
|
|
272
272
|
|
|
273
|
-
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
// Reset
|
|
277
|
-
const handleReset = () => {
|
|
278
|
-
// Clear form values
|
|
279
|
-
if (state.form != null) resetForm(state.form);
|
|
280
|
-
if (state.moreForm != null) resetForm(state.moreForm);
|
|
281
|
-
|
|
282
|
-
// Resubmit
|
|
283
|
-
handleSubmitInstant(true);
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
// Submit
|
|
287
|
-
const handleSubmit = (delay = 480) => {
|
|
288
|
-
if (state.submitSeed != null) {
|
|
289
|
-
clearTimeout(state.submitSeed);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// Delay the change
|
|
293
|
-
state.submitSeed = window.setTimeout(handleSubmitInstant, delay);
|
|
273
|
+
delayed.call();
|
|
294
274
|
};
|
|
295
275
|
|
|
296
276
|
// Submit at once
|
|
297
277
|
const handleSubmitInstant = (reset: boolean = false) => {
|
|
298
|
-
// Reset timeout
|
|
299
|
-
state.submitSeed = undefined;
|
|
300
|
-
|
|
301
278
|
// Prepare data
|
|
302
279
|
const data = new FormData(state.form);
|
|
303
280
|
if (state.moreForm != null) {
|
|
@@ -307,17 +284,25 @@ export function SearchBar(props: SearchBarProps) {
|
|
|
307
284
|
onSubmit(data, reset);
|
|
308
285
|
};
|
|
309
286
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
287
|
+
const delayed = useDelayedExecutor(handleSubmitInstant, 480);
|
|
288
|
+
|
|
289
|
+
// Reset
|
|
290
|
+
const handleReset = () => {
|
|
291
|
+
// Clear form values
|
|
292
|
+
if (state.form != null) resetForm(state.form);
|
|
293
|
+
if (state.moreForm != null) resetForm(state.moreForm);
|
|
294
|
+
|
|
295
|
+
// Resubmit
|
|
296
|
+
handleSubmitInstant(true);
|
|
297
|
+
};
|
|
317
298
|
|
|
318
299
|
React.useEffect(() => {
|
|
319
300
|
// Delayed way
|
|
320
|
-
|
|
301
|
+
delayed.call(100);
|
|
302
|
+
|
|
303
|
+
return () => {
|
|
304
|
+
delayed.clear();
|
|
305
|
+
};
|
|
321
306
|
}, [className]);
|
|
322
307
|
|
|
323
308
|
// Layout
|
package/src/mu/SearchField.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { TextField, TextFieldProps } from '@mui/material';
|
|
2
2
|
import React from 'react';
|
|
3
|
+
import { useDelayedExecutor } from '../uses/useDelayedExecutor';
|
|
3
4
|
import { MUGlobal } from './MUGlobal';
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -42,7 +43,10 @@ export function SearchField(props: SearchFieldProps) {
|
|
|
42
43
|
if (readOnly != null) InputProps.readOnly = readOnly;
|
|
43
44
|
|
|
44
45
|
const isMounted = React.useRef(true);
|
|
45
|
-
const
|
|
46
|
+
const delayed =
|
|
47
|
+
onChange != null && changeDelay != null && changeDelay >= 1
|
|
48
|
+
? useDelayedExecutor(onChange, changeDelay)
|
|
49
|
+
: undefined;
|
|
46
50
|
|
|
47
51
|
const onChangeEx = (
|
|
48
52
|
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
|
|
@@ -54,16 +58,13 @@ export function SearchField(props: SearchFieldProps) {
|
|
|
54
58
|
return;
|
|
55
59
|
}
|
|
56
60
|
|
|
57
|
-
|
|
58
|
-
delaySeed.current = window.setTimeout(() => {
|
|
59
|
-
if (isMounted.current) onChange(event);
|
|
60
|
-
}, changeDelay);
|
|
61
|
+
delayed?.call(undefined, event);
|
|
61
62
|
};
|
|
62
63
|
|
|
63
64
|
React.useEffect(() => {
|
|
64
65
|
return () => {
|
|
65
66
|
isMounted.current = false;
|
|
66
|
-
|
|
67
|
+
delayed?.clear();
|
|
67
68
|
};
|
|
68
69
|
}, []);
|
|
69
70
|
|
package/src/mu/TextFieldEx.tsx
CHANGED
|
@@ -9,6 +9,7 @@ import { MUGlobal } from './MUGlobal';
|
|
|
9
9
|
import { Clear, Visibility } from '@mui/icons-material';
|
|
10
10
|
import useCombinedRefs from '../uses/useCombinedRefs';
|
|
11
11
|
import { Keyboard } from '@etsoo/shared';
|
|
12
|
+
import { useDelayedExecutor } from '../uses/useDelayedExecutor';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Extended text field props
|
|
@@ -193,7 +194,10 @@ export const TextFieldEx = React.forwardRef<
|
|
|
193
194
|
);
|
|
194
195
|
|
|
195
196
|
const isMounted = React.useRef(true);
|
|
196
|
-
const
|
|
197
|
+
const delayed =
|
|
198
|
+
onChange != null && changeDelay != null && changeDelay >= 1
|
|
199
|
+
? useDelayedExecutor(onChange, changeDelay)
|
|
200
|
+
: undefined;
|
|
197
201
|
|
|
198
202
|
const onChangeEx = (
|
|
199
203
|
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
|
|
@@ -218,16 +222,13 @@ export const TextFieldEx = React.forwardRef<
|
|
|
218
222
|
return;
|
|
219
223
|
}
|
|
220
224
|
|
|
221
|
-
|
|
222
|
-
delaySeed.current = window.setTimeout(() => {
|
|
223
|
-
if (isMounted.current) onChange(event);
|
|
224
|
-
}, changeDelay);
|
|
225
|
+
delayed?.call(undefined, event);
|
|
225
226
|
};
|
|
226
227
|
|
|
227
228
|
React.useEffect(() => {
|
|
228
229
|
return () => {
|
|
229
230
|
isMounted.current = false;
|
|
230
|
-
|
|
231
|
+
delayed?.clear();
|
|
231
232
|
};
|
|
232
233
|
}, []);
|
|
233
234
|
|
package/src/mu/Tiplist.tsx
CHANGED
|
@@ -3,6 +3,7 @@ import { DataTypes } from '@etsoo/shared';
|
|
|
3
3
|
import { Autocomplete, AutocompleteRenderInputParams } from '@mui/material';
|
|
4
4
|
import React from 'react';
|
|
5
5
|
import { ReactUtils } from '../app/ReactUtils';
|
|
6
|
+
import { useDelayedExecutor } from '../uses/useDelayedExecutor';
|
|
6
7
|
import { AutocompleteExtendedProps } from './AutocompleteExtendedProps';
|
|
7
8
|
import { InputField } from './InputField';
|
|
8
9
|
import { SearchField } from './SearchField';
|
|
@@ -95,7 +96,6 @@ export function Tiplist<T extends {} = IdLabelDto>(props: TiplistProps<T>) {
|
|
|
95
96
|
|
|
96
97
|
// State
|
|
97
98
|
const [state] = React.useState<{
|
|
98
|
-
loadDataSeed?: number;
|
|
99
99
|
idLoaded?: boolean;
|
|
100
100
|
idSet?: boolean;
|
|
101
101
|
}>({});
|
|
@@ -125,24 +125,12 @@ export function Tiplist<T extends {} = IdLabelDto>(props: TiplistProps<T>) {
|
|
|
125
125
|
// Stop bubble
|
|
126
126
|
event.stopPropagation();
|
|
127
127
|
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
clearTimeout(state.loadDataSeed);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Delay 480 for loading data
|
|
134
|
-
state.loadDataSeed = window.setTimeout(
|
|
135
|
-
loadDataDirect,
|
|
136
|
-
480,
|
|
137
|
-
event.currentTarget.value
|
|
138
|
-
);
|
|
128
|
+
// Call with delay
|
|
129
|
+
delayed.call(undefined, event.currentTarget.value);
|
|
139
130
|
};
|
|
140
131
|
|
|
141
132
|
// Directly load data
|
|
142
133
|
const loadDataDirect = (keyword?: string, id?: DataTypes.IdType) => {
|
|
143
|
-
// Reset seed value
|
|
144
|
-
state.loadDataSeed = undefined;
|
|
145
|
-
|
|
146
134
|
// Reset options
|
|
147
135
|
// setOptions([]);
|
|
148
136
|
|
|
@@ -176,6 +164,8 @@ export function Tiplist<T extends {} = IdLabelDto>(props: TiplistProps<T>) {
|
|
|
176
164
|
});
|
|
177
165
|
};
|
|
178
166
|
|
|
167
|
+
const delayed = useDelayedExecutor(loadDataDirect, 480);
|
|
168
|
+
|
|
179
169
|
const setInputValue = (value: T | null) => {
|
|
180
170
|
stateUpdate({ value });
|
|
181
171
|
|
|
@@ -207,12 +197,8 @@ export function Tiplist<T extends {} = IdLabelDto>(props: TiplistProps<T>) {
|
|
|
207
197
|
|
|
208
198
|
React.useEffect(() => {
|
|
209
199
|
return () => {
|
|
210
|
-
// Clear any seed
|
|
211
|
-
if (state.loadDataSeed != null) {
|
|
212
|
-
clearTimeout(state.loadDataSeed);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
200
|
isMounted.current = false;
|
|
201
|
+
delayed.clear();
|
|
216
202
|
};
|
|
217
203
|
}, []);
|
|
218
204
|
|
package/src/mu/TooltipClick.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ClickAwayListener, Tooltip, TooltipProps } from '@mui/material';
|
|
2
2
|
import React from 'react';
|
|
3
|
+
import { useDelayedExecutor } from '../uses/useDelayedExecutor';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Tooltip with click visibility props
|
|
@@ -36,32 +37,22 @@ export function TooltipClick(props: TooltipClickProps) {
|
|
|
36
37
|
// State
|
|
37
38
|
const [localTitle, setTitle] = React.useState(title);
|
|
38
39
|
const [open, setOpen] = React.useState(false);
|
|
39
|
-
|
|
40
|
+
|
|
41
|
+
const delayed =
|
|
42
|
+
leaveDelay > 0
|
|
43
|
+
? useDelayedExecutor(() => setOpen(false), leaveDelay)
|
|
44
|
+
: undefined;
|
|
40
45
|
|
|
41
46
|
// Callback for open the tooltip
|
|
42
47
|
const openTooltip = (newTitle?: string) => {
|
|
43
48
|
setOpen(true);
|
|
44
49
|
if (newTitle) setTitle(newTitle);
|
|
45
|
-
|
|
46
|
-
if (leaveDelay > 0) {
|
|
47
|
-
clearLeaveSeed();
|
|
48
|
-
leaveSeed.current = window.setTimeout(
|
|
49
|
-
() => setOpen(false),
|
|
50
|
-
leaveDelay
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
const clearLeaveSeed = () => {
|
|
56
|
-
if (leaveSeed.current > 0) {
|
|
57
|
-
window.clearTimeout(leaveSeed.current);
|
|
58
|
-
leaveSeed.current = 0;
|
|
59
|
-
}
|
|
50
|
+
delayed?.call();
|
|
60
51
|
};
|
|
61
52
|
|
|
62
53
|
React.useEffect(() => {
|
|
63
54
|
return () => {
|
|
64
|
-
|
|
55
|
+
delayed?.clear();
|
|
65
56
|
};
|
|
66
57
|
}, []);
|
|
67
58
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { DelayedExecutorType, ExtendUtils } from '@etsoo/shared';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Create delayed executor
|
|
6
|
+
*/
|
|
7
|
+
export function useDelayedExecutor<P extends any[]>(
|
|
8
|
+
func: (...args: P) => void,
|
|
9
|
+
delayMiliseconcs: number
|
|
10
|
+
) {
|
|
11
|
+
const ref = React.useRef<DelayedExecutorType<P>>();
|
|
12
|
+
if (ref.current == null)
|
|
13
|
+
ref.current = ExtendUtils.delayedExecutor(func, delayMiliseconcs);
|
|
14
|
+
return ref.current;
|
|
15
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { DomUtils } from '@etsoo/shared';
|
|
2
2
|
import React from 'react';
|
|
3
|
+
import { useDelayedExecutor } from './useDelayedExecutor';
|
|
3
4
|
|
|
4
5
|
interface states {
|
|
5
6
|
count: number;
|
|
6
7
|
indices: number[];
|
|
7
|
-
seed?: number;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -29,10 +29,51 @@ export function useDimensions(
|
|
|
29
29
|
indices: []
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
+
// Dimentions
|
|
33
|
+
const dimensions =
|
|
34
|
+
React.useRef<[React.RefCallback<Element>, Element?, DOMRect?][]>();
|
|
35
|
+
if (dimensions.current == null) {
|
|
36
|
+
// Init
|
|
37
|
+
const init: [React.RefCallback<Element>, Element?, DOMRect?][] = [];
|
|
38
|
+
for (let e = 0; e < elements; e++) {
|
|
39
|
+
init.push(
|
|
40
|
+
((index): [React.RefCallback<Element>] => {
|
|
41
|
+
return [
|
|
42
|
+
(instance) => {
|
|
43
|
+
if (instance != null) {
|
|
44
|
+
// Current element
|
|
45
|
+
const currentElement = init[index][1];
|
|
46
|
+
|
|
47
|
+
if (currentElement != null) {
|
|
48
|
+
// Same target, return
|
|
49
|
+
if (currentElement == instance) return;
|
|
50
|
+
|
|
51
|
+
// Cancel observation
|
|
52
|
+
resizeObserver.unobserve(currentElement);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Update element
|
|
56
|
+
init[index][1] = instance;
|
|
57
|
+
|
|
58
|
+
// Start observe
|
|
59
|
+
resizeObserver.observe(instance);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
];
|
|
63
|
+
})(e)
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
dimensions.current = init;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const delayed = useDelayedExecutor(setState, miliseconds);
|
|
70
|
+
|
|
32
71
|
// Observer
|
|
33
72
|
const resizeObserver = new ResizeObserver((entries) => {
|
|
34
73
|
// Update Rect
|
|
35
74
|
const indices: number[] = [];
|
|
75
|
+
const init = dimensions.current!;
|
|
76
|
+
|
|
36
77
|
entries.forEach((entry) => {
|
|
37
78
|
const index = init.findIndex((item) => item[1] === entry.target);
|
|
38
79
|
if (index !== -1) {
|
|
@@ -62,70 +103,21 @@ export function useDimensions(
|
|
|
62
103
|
|
|
63
104
|
// Update state
|
|
64
105
|
if (indices.length > 0) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
clearTimeout(state.seed);
|
|
69
|
-
}
|
|
70
|
-
state.seed = window.setTimeout(
|
|
71
|
-
(localUpdate: states) => {
|
|
72
|
-
setState(localUpdate);
|
|
73
|
-
},
|
|
74
|
-
miliseconds,
|
|
75
|
-
update
|
|
76
|
-
);
|
|
77
|
-
} else {
|
|
78
|
-
setState(update);
|
|
79
|
-
}
|
|
106
|
+
// Count only for unique update
|
|
107
|
+
const update = { count: state.count + 1, indices };
|
|
108
|
+
delayed.call(undefined, update);
|
|
80
109
|
}
|
|
81
110
|
});
|
|
82
111
|
|
|
83
|
-
// Init
|
|
84
|
-
const init: [React.RefCallback<Element>, Element?, DOMRect?][] = [];
|
|
85
|
-
for (let e = 0; e < elements; e++) {
|
|
86
|
-
init.push(
|
|
87
|
-
((index): [React.RefCallback<Element>] => {
|
|
88
|
-
return [
|
|
89
|
-
(instance) => {
|
|
90
|
-
if (instance != null) {
|
|
91
|
-
// Current element
|
|
92
|
-
const currentElement = init[index][1];
|
|
93
|
-
|
|
94
|
-
if (currentElement != null) {
|
|
95
|
-
// Same target, return
|
|
96
|
-
if (currentElement == instance) return;
|
|
97
|
-
|
|
98
|
-
// Cancel observation
|
|
99
|
-
resizeObserver.unobserve(currentElement);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Update element
|
|
103
|
-
init[index][1] = instance;
|
|
104
|
-
|
|
105
|
-
// Start observe
|
|
106
|
-
resizeObserver.observe(instance);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
];
|
|
110
|
-
})(e)
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Dimensions
|
|
115
|
-
const [dimensions] = React.useState(init);
|
|
116
|
-
|
|
117
112
|
// Layout ready
|
|
118
113
|
React.useEffect(() => {
|
|
119
114
|
return () => {
|
|
120
115
|
// Clear the observer
|
|
121
116
|
resizeObserver.disconnect();
|
|
122
|
-
|
|
123
|
-
if (state.seed != null) {
|
|
124
|
-
clearTimeout(state.seed);
|
|
125
|
-
}
|
|
117
|
+
delayed.clear();
|
|
126
118
|
};
|
|
127
119
|
}, []);
|
|
128
120
|
|
|
129
121
|
// Return
|
|
130
|
-
return { dimensions, state };
|
|
122
|
+
return { dimensions: dimensions.current, state };
|
|
131
123
|
}
|
package/src/uses/useTimeout.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ExtendUtils } from '@etsoo/shared';
|
|
1
2
|
import React from 'react';
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -5,31 +6,24 @@ import React from 'react';
|
|
|
5
6
|
* @param action Action function
|
|
6
7
|
* @param milliseconds Interval of milliseconds
|
|
7
8
|
*/
|
|
8
|
-
export const useTimeout = (
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
//
|
|
13
|
-
const
|
|
14
|
-
if (seed > 0) {
|
|
15
|
-
clearTimeout(seed);
|
|
16
|
-
seed = 0;
|
|
17
|
-
}
|
|
18
|
-
};
|
|
9
|
+
export const useTimeout = (
|
|
10
|
+
action: (...args: any[]) => void,
|
|
11
|
+
milliseconds: number
|
|
12
|
+
) => {
|
|
13
|
+
// Delayed
|
|
14
|
+
const d = ExtendUtils.delayedExecutor(action, milliseconds);
|
|
19
15
|
|
|
20
16
|
// Merge into the life cycle
|
|
21
17
|
React.useEffect(() => {
|
|
22
|
-
|
|
23
|
-
action.call(null);
|
|
24
|
-
}, milliseconds);
|
|
18
|
+
d.call();
|
|
25
19
|
|
|
26
20
|
return () => {
|
|
27
|
-
|
|
21
|
+
d.clear();
|
|
28
22
|
};
|
|
29
23
|
}, []);
|
|
30
24
|
|
|
31
25
|
// Return cancel method
|
|
32
26
|
return {
|
|
33
|
-
cancel
|
|
27
|
+
cancel: d.clear
|
|
34
28
|
};
|
|
35
|
-
};
|
|
29
|
+
};
|