funda-ui 4.7.108 → 4.7.115
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/Stepper/index.css +30 -22
- package/Stepper/index.js +11 -5
- package/Utils/useHistoryTracker.d.ts +26 -0
- package/Utils/useHistoryTracker.js +475 -0
- package/lib/cjs/Stepper/index.js +11 -5
- package/lib/cjs/Utils/useHistoryTracker.d.ts +26 -0
- package/lib/cjs/Utils/useHistoryTracker.js +475 -0
- package/lib/css/Stepper/index.css +30 -22
- package/lib/esm/Stepper/index.scss +33 -23
- package/lib/esm/Stepper/index.tsx +18 -5
- package/lib/esm/Utils/hooks/useHistoryTracker.tsx +403 -0
- package/package.json +1 -1
- package/Utils/useGlobalUrlListener.d.ts +0 -2
- package/Utils/useGlobalUrlListener.js +0 -157
- package/lib/cjs/Utils/useGlobalUrlListener.d.ts +0 -2
- package/lib/cjs/Utils/useGlobalUrlListener.js +0 -157
- package/lib/esm/Utils/hooks/useGlobalUrlListener.tsx +0 -46
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
|
|
2
|
-
|
|
3
2
|
/* ======================================================
|
|
4
3
|
<!-- Stepper -->
|
|
5
4
|
/* ====================================================== */
|
|
@@ -18,9 +17,11 @@
|
|
|
18
17
|
--stepper-line-default: #dfdfdf;
|
|
19
18
|
--stepper-line-active: #2563eb;
|
|
20
19
|
--stepper-line-complete: #22c55e;
|
|
21
|
-
--stepper-indicator-size: 0.875rem;
|
|
22
|
-
--stepper-indicator-offset:
|
|
20
|
+
--stepper-indicator-font-size: 0.875rem;
|
|
21
|
+
--stepper-indicator-offset: 60px;
|
|
23
22
|
--stepper-title-size: 0.875rem;
|
|
23
|
+
--stepper-indicator-size: 32px;
|
|
24
|
+
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
position: relative;
|
|
@@ -31,25 +32,27 @@
|
|
|
31
32
|
align-items: flex-start;
|
|
32
33
|
flex-wrap: nowrap;
|
|
33
34
|
position: relative;
|
|
35
|
+
max-width: calc(100% - var(--stepper-indicator-size));
|
|
34
36
|
|
|
35
37
|
/* background line */
|
|
36
38
|
&::before {
|
|
37
39
|
content: '';
|
|
38
40
|
position: absolute;
|
|
39
41
|
top: 16px; /* Subtract the height of the title */
|
|
40
|
-
left:
|
|
42
|
+
left: 0;
|
|
41
43
|
right: 0;
|
|
42
44
|
height: 2px;
|
|
43
45
|
background-color: var(--stepper-line-default);
|
|
44
46
|
z-index: 1;
|
|
45
|
-
width:
|
|
47
|
+
width: 100%;
|
|
48
|
+
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
&::after {
|
|
49
52
|
content: '';
|
|
50
53
|
position: absolute;
|
|
51
54
|
top: 16px; /* Subtract the height of the title */
|
|
52
|
-
left:
|
|
55
|
+
left: 0;
|
|
53
56
|
height: 2px;
|
|
54
57
|
background-color: var(--stepper-line-complete);
|
|
55
58
|
z-index: 2;
|
|
@@ -59,7 +62,7 @@
|
|
|
59
62
|
|
|
60
63
|
&::after {
|
|
61
64
|
width: var(--stepper-progress-width, 0%);
|
|
62
|
-
max-width:
|
|
65
|
+
max-width: 100%;
|
|
63
66
|
}
|
|
64
67
|
}
|
|
65
68
|
|
|
@@ -69,7 +72,7 @@
|
|
|
69
72
|
display: flex;
|
|
70
73
|
flex-direction: column;
|
|
71
74
|
align-items: center;
|
|
72
|
-
max-width:
|
|
75
|
+
max-width: 0;
|
|
73
76
|
position: relative;
|
|
74
77
|
z-index: 3;
|
|
75
78
|
|
|
@@ -123,15 +126,15 @@
|
|
|
123
126
|
|
|
124
127
|
/* Indicator */
|
|
125
128
|
.step-indicator {
|
|
126
|
-
width:
|
|
127
|
-
height:
|
|
129
|
+
width: var(--stepper-indicator-size);
|
|
130
|
+
height: var(--stepper-indicator-size);
|
|
128
131
|
margin: 0 auto 0.25rem;
|
|
129
132
|
border-radius: 9999px;
|
|
130
133
|
display: flex;
|
|
131
134
|
align-items: center;
|
|
132
135
|
justify-content: center;
|
|
133
136
|
border: 2px solid #ccc;
|
|
134
|
-
font-size: var(--stepper-indicator-size);
|
|
137
|
+
font-size: var(--stepper-indicator-font-size);
|
|
135
138
|
|
|
136
139
|
/* default */
|
|
137
140
|
background-color: var(--stepper-indicator-default);
|
|
@@ -155,13 +158,19 @@
|
|
|
155
158
|
|
|
156
159
|
/* Title */
|
|
157
160
|
.step-title {
|
|
161
|
+
white-space: nowrap;
|
|
158
162
|
font-size: var(--stepper-title-size);
|
|
163
|
+
margin-left: calc(var(--stepper-indicator-offset) / 2);
|
|
159
164
|
|
|
160
165
|
/* default */
|
|
161
166
|
color: var(--stepper-color-default);
|
|
162
167
|
|
|
163
168
|
&--active {
|
|
164
|
-
|
|
169
|
+
color: var(--stepper-indicator-active);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
&--complete {
|
|
173
|
+
color: var(--stepper-indicator-complete);
|
|
165
174
|
}
|
|
166
175
|
}
|
|
167
176
|
|
|
@@ -201,20 +210,20 @@
|
|
|
201
210
|
/*------ Verticle ------*/
|
|
202
211
|
.stepper-container.stepper-container--vertical {
|
|
203
212
|
|
|
204
|
-
--stepper-indicator-offset: 50px;
|
|
205
213
|
|
|
206
214
|
display: flex;
|
|
207
215
|
flex-direction: column;
|
|
208
|
-
gap:
|
|
216
|
+
gap: 2.5rem; /* line length */
|
|
217
|
+
max-height: calc(100% - var(--stepper-indicator-size));
|
|
209
218
|
|
|
210
219
|
/* background line */
|
|
211
220
|
&::before {
|
|
212
221
|
content: '';
|
|
213
222
|
position: absolute;
|
|
214
|
-
top:
|
|
215
|
-
left:
|
|
223
|
+
top: 0;
|
|
224
|
+
left: calc(var(--stepper-indicator-size) / 2);
|
|
216
225
|
width: 2px;
|
|
217
|
-
height:
|
|
226
|
+
height: 100%;
|
|
218
227
|
background-color: var(--stepper-line-default);
|
|
219
228
|
z-index: 1;
|
|
220
229
|
}
|
|
@@ -222,8 +231,8 @@
|
|
|
222
231
|
&::after {
|
|
223
232
|
content: '';
|
|
224
233
|
position: absolute;
|
|
225
|
-
top:
|
|
226
|
-
left:
|
|
234
|
+
top: 0;
|
|
235
|
+
left: calc(var(--stepper-indicator-size) / 2);
|
|
227
236
|
width: 2px;
|
|
228
237
|
background-color: var(--stepper-line-complete);
|
|
229
238
|
z-index: 2;
|
|
@@ -233,7 +242,7 @@
|
|
|
233
242
|
|
|
234
243
|
&::after {
|
|
235
244
|
height: var(--stepper-progress-height, 0%);
|
|
236
|
-
max-height:
|
|
245
|
+
max-height: 100%;
|
|
237
246
|
}
|
|
238
247
|
|
|
239
248
|
|
|
@@ -244,12 +253,13 @@
|
|
|
244
253
|
}
|
|
245
254
|
.vertical-step-left {
|
|
246
255
|
flex-shrink: 0;
|
|
247
|
-
width: var(--stepper-indicator-
|
|
256
|
+
width: var(--stepper-indicator-size);
|
|
257
|
+
height: var(--stepper-indicator-size);
|
|
248
258
|
position: relative;
|
|
249
259
|
|
|
250
260
|
/* Main Navigation - Each step item (with circle + title) */
|
|
251
261
|
.step-item {
|
|
252
|
-
margin-top:
|
|
262
|
+
margin-top: 0;
|
|
253
263
|
}
|
|
254
264
|
|
|
255
265
|
/* Line */
|
|
@@ -266,7 +276,7 @@
|
|
|
266
276
|
left: 70px;
|
|
267
277
|
width: calc(100% - 70px);
|
|
268
278
|
position: absolute;
|
|
269
|
-
top:
|
|
279
|
+
top: 0; /* required */
|
|
270
280
|
}
|
|
271
281
|
|
|
272
282
|
/* Title */
|
|
@@ -131,7 +131,8 @@ const Stepper = forwardRef<StepperRef, StepperProps>((props, ref) => {
|
|
|
131
131
|
className={combinedCls(
|
|
132
132
|
'step-title',
|
|
133
133
|
{
|
|
134
|
-
'step-title--active': isActive
|
|
134
|
+
'step-title--active': isActive,
|
|
135
|
+
'step-title--complete': isCompleted
|
|
135
136
|
}
|
|
136
137
|
)}
|
|
137
138
|
>
|
|
@@ -168,7 +169,16 @@ const Stepper = forwardRef<StepperRef, StepperProps>((props, ref) => {
|
|
|
168
169
|
const isCompleted = index < activeIndex || (index === panels.length - 1 && isLastStepComplete);
|
|
169
170
|
|
|
170
171
|
return (
|
|
171
|
-
<div
|
|
172
|
+
<div
|
|
173
|
+
key={index}
|
|
174
|
+
className={combinedCls(
|
|
175
|
+
'vertical-step-row',
|
|
176
|
+
{
|
|
177
|
+
'vertical-step-row--active': isActive,
|
|
178
|
+
'vertical-step-row--complete': isCompleted
|
|
179
|
+
}
|
|
180
|
+
)}
|
|
181
|
+
>
|
|
172
182
|
{/* Left */}
|
|
173
183
|
<div className="vertical-step-left">
|
|
174
184
|
<div
|
|
@@ -199,7 +209,8 @@ const Stepper = forwardRef<StepperRef, StepperProps>((props, ref) => {
|
|
|
199
209
|
className={combinedCls(
|
|
200
210
|
'step-title',
|
|
201
211
|
{
|
|
202
|
-
'step-title--active': isActive
|
|
212
|
+
'step-title--active': isActive,
|
|
213
|
+
'step-title--complete': isCompleted
|
|
203
214
|
}
|
|
204
215
|
)}
|
|
205
216
|
>
|
|
@@ -246,6 +257,8 @@ const Stepper = forwardRef<StepperRef, StepperProps>((props, ref) => {
|
|
|
246
257
|
'--stepper-progress-height': `${progress}%`
|
|
247
258
|
} as React.CSSProperties;
|
|
248
259
|
} else {
|
|
260
|
+
|
|
261
|
+
const defaultProgress = (activeIndex / (panels.length - 1)) * 100;
|
|
249
262
|
const firstItem = stepItems[0] as HTMLDivElement;
|
|
250
263
|
const lastItem = stepItems[stepItems.length - 1] as HTMLDivElement;
|
|
251
264
|
if (!firstItem || !lastItem) return {};
|
|
@@ -258,10 +271,10 @@ const Stepper = forwardRef<StepperRef, StepperProps>((props, ref) => {
|
|
|
258
271
|
if (!currentItem) return {};
|
|
259
272
|
|
|
260
273
|
const currentCenter = currentItem.offsetLeft + (currentItem.clientWidth / 2);
|
|
261
|
-
const progress = ((currentCenter - firstCenter) / totalWidth) * 100;
|
|
274
|
+
const progress = defaultProgress !== 0 && activeIndex > 0 ? defaultProgress : ((currentCenter - firstCenter) / totalWidth) * 100;
|
|
262
275
|
|
|
263
276
|
return {
|
|
264
|
-
'--stepper-progress-width': `${progress}%`
|
|
277
|
+
'--stepper-progress-width': `${isNaN(progress) ? 0 : progress}%`
|
|
265
278
|
} as React.CSSProperties;
|
|
266
279
|
}
|
|
267
280
|
};
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* History Tracker
|
|
3
|
+
*
|
|
4
|
+
* @usage:
|
|
5
|
+
|
|
6
|
+
const App = () => {
|
|
7
|
+
const {
|
|
8
|
+
history,
|
|
9
|
+
forwardHistory,
|
|
10
|
+
currentUrl,
|
|
11
|
+
firstUrl,
|
|
12
|
+
clearHistory,
|
|
13
|
+
goBack
|
|
14
|
+
} = useHistoryTracker({
|
|
15
|
+
onChange: ({
|
|
16
|
+
isReady,
|
|
17
|
+
history,
|
|
18
|
+
forwardHistory,
|
|
19
|
+
currentUrl,
|
|
20
|
+
firstUrl,
|
|
21
|
+
canGoBack,
|
|
22
|
+
canGoForward
|
|
23
|
+
} : {
|
|
24
|
+
isReady: boolean;
|
|
25
|
+
history: string[];
|
|
26
|
+
forwardHistory: string[];
|
|
27
|
+
currentUrl: string;
|
|
28
|
+
firstUrl: string;
|
|
29
|
+
canGoBack: boolean;
|
|
30
|
+
canGoForward: boolean;
|
|
31
|
+
}) => {
|
|
32
|
+
console.log('--> onChange: ',
|
|
33
|
+
isReady,
|
|
34
|
+
history,
|
|
35
|
+
forwardHistory,
|
|
36
|
+
currentUrl,
|
|
37
|
+
firstUrl,
|
|
38
|
+
canGoBack,
|
|
39
|
+
canGoForward
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div>
|
|
46
|
+
|
|
47
|
+
<div>
|
|
48
|
+
<h3>First URL:</h3>
|
|
49
|
+
<p>{firstUrl}</p>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<div>
|
|
53
|
+
<h3>Current URL:</h3>
|
|
54
|
+
<p>{currentUrl}</p>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<div>
|
|
58
|
+
<h3>History ({history.length}):</h3>
|
|
59
|
+
<ul>
|
|
60
|
+
{history.map((url, index) => (
|
|
61
|
+
<li key={index}>{url}</li>
|
|
62
|
+
))}
|
|
63
|
+
</ul>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<div>
|
|
67
|
+
<h3>Forward History ({forwardHistory.length}):</h3>
|
|
68
|
+
<ul>
|
|
69
|
+
{forwardHistory.map((url, index) => (
|
|
70
|
+
<li key={index}>{url}</li>
|
|
71
|
+
))}
|
|
72
|
+
</ul>
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
<button onClick={clearHistory}>
|
|
78
|
+
Clear History
|
|
79
|
+
</button>
|
|
80
|
+
|
|
81
|
+
<button onClick={async () => {
|
|
82
|
+
try {
|
|
83
|
+
const {
|
|
84
|
+
isReady,
|
|
85
|
+
history,
|
|
86
|
+
forwardHistory,
|
|
87
|
+
canGoBack,
|
|
88
|
+
canGoForward
|
|
89
|
+
} : {
|
|
90
|
+
isReady: boolean;
|
|
91
|
+
history: string[];
|
|
92
|
+
forwardHistory: string[];
|
|
93
|
+
canGoBack: boolean;
|
|
94
|
+
canGoForward: boolean;
|
|
95
|
+
} = await goBack();
|
|
96
|
+
|
|
97
|
+
console.log('--> goBack: ',
|
|
98
|
+
isReady,
|
|
99
|
+
history,
|
|
100
|
+
forwardHistory,
|
|
101
|
+
currentUrl,
|
|
102
|
+
firstUrl,
|
|
103
|
+
canGoBack,
|
|
104
|
+
canGoForward
|
|
105
|
+
);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.error('Navigation failed', error);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
}}>
|
|
111
|
+
Back
|
|
112
|
+
</button>
|
|
113
|
+
|
|
114
|
+
</div>
|
|
115
|
+
);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
*/
|
|
119
|
+
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
120
|
+
|
|
121
|
+
export type UseHistoryTrackerChangeFnType = (args: {
|
|
122
|
+
isReady: boolean;
|
|
123
|
+
history: string[];
|
|
124
|
+
forwardHistory: string[],
|
|
125
|
+
currentUrl: string;
|
|
126
|
+
firstUrl: string;
|
|
127
|
+
canGoBack: boolean;
|
|
128
|
+
canGoForward: boolean;
|
|
129
|
+
}) => void;
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
export type UseHistoryTrackerProps = {
|
|
133
|
+
onChange?: UseHistoryTrackerChangeFnType | null;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// Create a secure version of useLayoutEffect that is downgraded to useEffect when SSR
|
|
137
|
+
const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
|
|
138
|
+
|
|
139
|
+
const useHistoryTracker = (props: UseHistoryTrackerProps) => {
|
|
140
|
+
const {
|
|
141
|
+
onChange
|
|
142
|
+
} = props;
|
|
143
|
+
|
|
144
|
+
const [isReady, setIsReady] = useState<boolean>(false);
|
|
145
|
+
const historyRef = useRef<string[]>([]);
|
|
146
|
+
const forwardHistoryRef = useRef<string[]>([]);
|
|
147
|
+
const firstUrlRef = useRef<string>('');
|
|
148
|
+
const [currentUrl, setCurrentUrl] = useState<string>('');
|
|
149
|
+
|
|
150
|
+
const initialize = useCallback(() => {
|
|
151
|
+
if (typeof window === 'undefined') return;
|
|
152
|
+
|
|
153
|
+
const currentLocation = window.location.href as string;
|
|
154
|
+
|
|
155
|
+
// If the history is empty, set the first record
|
|
156
|
+
if (historyRef.current.length === 0) {
|
|
157
|
+
firstUrlRef.current = currentLocation;
|
|
158
|
+
historyRef.current = [currentLocation];
|
|
159
|
+
setCurrentUrl(currentLocation);
|
|
160
|
+
|
|
161
|
+
onChange?.({
|
|
162
|
+
isReady: false,
|
|
163
|
+
history: [currentLocation],
|
|
164
|
+
forwardHistory: [],
|
|
165
|
+
currentUrl: currentLocation,
|
|
166
|
+
firstUrl: currentLocation,
|
|
167
|
+
canGoBack: false,
|
|
168
|
+
canGoForward: false
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
setIsReady(true);
|
|
174
|
+
}, []);
|
|
175
|
+
|
|
176
|
+
useIsomorphicLayoutEffect(() => {
|
|
177
|
+
initialize();
|
|
178
|
+
}, [initialize]);
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
const clearHistory = useCallback(() => {
|
|
182
|
+
if (typeof window === 'undefined') return;
|
|
183
|
+
|
|
184
|
+
historyRef.current = [];
|
|
185
|
+
forwardHistoryRef.current = [];
|
|
186
|
+
firstUrlRef.current = '';
|
|
187
|
+
setCurrentUrl('');
|
|
188
|
+
|
|
189
|
+
onChange?.({
|
|
190
|
+
isReady: true,
|
|
191
|
+
history: [],
|
|
192
|
+
forwardHistory: [],
|
|
193
|
+
currentUrl: '',
|
|
194
|
+
firstUrl: '',
|
|
195
|
+
canGoBack: false,
|
|
196
|
+
canGoForward: false
|
|
197
|
+
|
|
198
|
+
});
|
|
199
|
+
}, [onChange]); // only "onChange"
|
|
200
|
+
|
|
201
|
+
const goToHistory = useCallback((index: number) => {
|
|
202
|
+
if (typeof window === 'undefined') return;
|
|
203
|
+
if (index < 0 || index >= historyRef.current.length) return;
|
|
204
|
+
|
|
205
|
+
const targetUrl = historyRef.current[index];
|
|
206
|
+
if (targetUrl && targetUrl !== window.location.href) {
|
|
207
|
+
window.location.href = targetUrl;
|
|
208
|
+
}
|
|
209
|
+
}, []);
|
|
210
|
+
|
|
211
|
+
const goBack = useCallback(() => {
|
|
212
|
+
if (typeof window === 'undefined') return Promise.reject('Window is undefined');
|
|
213
|
+
if (historyRef.current.length <= 1) return Promise.reject('Cannot go back');
|
|
214
|
+
|
|
215
|
+
return new Promise((resolve) => {
|
|
216
|
+
// Moves the current URL into the forward history
|
|
217
|
+
const removedUrl = historyRef.current.pop() as string;
|
|
218
|
+
forwardHistoryRef.current.push(removedUrl);
|
|
219
|
+
|
|
220
|
+
const newCurrentUrl = historyRef.current[historyRef.current.length - 1];
|
|
221
|
+
setCurrentUrl(newCurrentUrl);
|
|
222
|
+
|
|
223
|
+
// Create initial data object
|
|
224
|
+
const data = {
|
|
225
|
+
isReady: true,
|
|
226
|
+
history: [...historyRef.current],
|
|
227
|
+
forwardHistory: [...forwardHistoryRef.current],
|
|
228
|
+
currentUrl: newCurrentUrl,
|
|
229
|
+
firstUrl: firstUrlRef.current,
|
|
230
|
+
canGoBack: canGoBack(),
|
|
231
|
+
canGoForward: canGoForward()
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// Notify about the history change
|
|
235
|
+
onChange?.(data);
|
|
236
|
+
|
|
237
|
+
// Create one-time listener for popstate
|
|
238
|
+
const handlePopState = () => {
|
|
239
|
+
// Remove the listener after it's called
|
|
240
|
+
window.removeEventListener('popstate', handlePopState);
|
|
241
|
+
|
|
242
|
+
// Get the final data after URL has changed
|
|
243
|
+
const finalData = {
|
|
244
|
+
isReady: true,
|
|
245
|
+
history: [...historyRef.current],
|
|
246
|
+
forwardHistory: [...forwardHistoryRef.current],
|
|
247
|
+
currentUrl: window.location.href,
|
|
248
|
+
firstUrl: firstUrlRef.current,
|
|
249
|
+
canGoBack: canGoBack(),
|
|
250
|
+
canGoForward: canGoForward()
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
resolve(finalData);
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
// Add the listener
|
|
257
|
+
window.addEventListener('popstate', handlePopState);
|
|
258
|
+
|
|
259
|
+
// Trigger the navigation
|
|
260
|
+
window.history.go(-1);
|
|
261
|
+
});
|
|
262
|
+
}, [onChange]);
|
|
263
|
+
|
|
264
|
+
const goForward = useCallback(() => {
|
|
265
|
+
if (typeof window === 'undefined') return Promise.reject('Window is undefined');
|
|
266
|
+
if (forwardHistoryRef.current.length === 0) return Promise.reject('Cannot go forward');
|
|
267
|
+
|
|
268
|
+
return new Promise((resolve) => {
|
|
269
|
+
// Take the URL from the forward history and add it to the main history
|
|
270
|
+
const nextUrl = forwardHistoryRef.current.pop() as string;
|
|
271
|
+
historyRef.current.push(nextUrl);
|
|
272
|
+
setCurrentUrl(nextUrl);
|
|
273
|
+
|
|
274
|
+
// Create initial data object
|
|
275
|
+
const data = {
|
|
276
|
+
isReady: true,
|
|
277
|
+
history: [...historyRef.current],
|
|
278
|
+
forwardHistory: [...forwardHistoryRef.current],
|
|
279
|
+
currentUrl: nextUrl,
|
|
280
|
+
firstUrl: firstUrlRef.current,
|
|
281
|
+
canGoBack: canGoBack(),
|
|
282
|
+
canGoForward: canGoForward()
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
onChange?.(data);
|
|
286
|
+
|
|
287
|
+
// Create one-time listener for popstate
|
|
288
|
+
const handlePopState = () => {
|
|
289
|
+
// Remove the listener after it's called
|
|
290
|
+
window.removeEventListener('popstate', handlePopState);
|
|
291
|
+
|
|
292
|
+
// Get the final data after URL has changed
|
|
293
|
+
const finalData = {
|
|
294
|
+
isReady: true,
|
|
295
|
+
history: [...historyRef.current],
|
|
296
|
+
forwardHistory: [...forwardHistoryRef.current],
|
|
297
|
+
currentUrl: window.location.href,
|
|
298
|
+
firstUrl: firstUrlRef.current,
|
|
299
|
+
canGoBack: canGoBack(),
|
|
300
|
+
canGoForward: canGoForward()
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
resolve(finalData);
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
// Add the listener
|
|
307
|
+
window.addEventListener('popstate', handlePopState);
|
|
308
|
+
|
|
309
|
+
// Trigger the navigation
|
|
310
|
+
window.history.go(1);
|
|
311
|
+
});
|
|
312
|
+
}, [onChange]);
|
|
313
|
+
|
|
314
|
+
const canGoBack = useCallback(() => {
|
|
315
|
+
return historyRef.current.length > 1;
|
|
316
|
+
}, []);
|
|
317
|
+
|
|
318
|
+
const canGoForward = useCallback(() => {
|
|
319
|
+
return forwardHistoryRef.current.length > 0;
|
|
320
|
+
}, []);
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
const handleUrlChange = useCallback(() => {
|
|
324
|
+
if (typeof window === 'undefined') return;
|
|
325
|
+
|
|
326
|
+
const newUrl = window.location.href;
|
|
327
|
+
|
|
328
|
+
// If the history is empty, set to the first URL
|
|
329
|
+
if (historyRef.current.length === 0) {
|
|
330
|
+
firstUrlRef.current = newUrl;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Avoid recording the same URL
|
|
334
|
+
if (historyRef.current[historyRef.current.length - 1] !== newUrl) {
|
|
335
|
+
historyRef.current.push(newUrl);
|
|
336
|
+
|
|
337
|
+
// Clear the advance history, as new navigation invalidates the advance history
|
|
338
|
+
forwardHistoryRef.current = [];
|
|
339
|
+
setCurrentUrl(newUrl);
|
|
340
|
+
|
|
341
|
+
onChange?.({
|
|
342
|
+
isReady: true,
|
|
343
|
+
history: [...historyRef.current],
|
|
344
|
+
forwardHistory: [...forwardHistoryRef.current],
|
|
345
|
+
currentUrl: newUrl,
|
|
346
|
+
firstUrl: firstUrlRef.current || newUrl, // Make sure there is always a value
|
|
347
|
+
canGoBack: canGoBack(),
|
|
348
|
+
canGoForward: canGoForward()
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
}, [onChange]); // only "onChange"
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
useEffect(() => {
|
|
355
|
+
if (typeof window === 'undefined') return;
|
|
356
|
+
|
|
357
|
+
// Listen for popstate events (browser forward/back)
|
|
358
|
+
window.addEventListener('popstate', handleUrlChange);
|
|
359
|
+
|
|
360
|
+
// Listen for hashchange events
|
|
361
|
+
window.addEventListener('hashchange', handleUrlChange);
|
|
362
|
+
|
|
363
|
+
// Listen for DOM and property changes
|
|
364
|
+
const observer = new MutationObserver((mutations) => {
|
|
365
|
+
mutations.forEach((mutation) => {
|
|
366
|
+
if (mutation.type === 'childList' || mutation.type === 'attributes') {
|
|
367
|
+
handleUrlChange();
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
observer.observe(document.body, {
|
|
374
|
+
childList: true, // monitor the addition and deletion of child nodes
|
|
375
|
+
subtree: true, // monitor all descendant nodes
|
|
376
|
+
attributes: true, // monitor attribute changes
|
|
377
|
+
attributeFilter: ['href'] // only monitor changes in the href attribute
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
return () => {
|
|
381
|
+
window.removeEventListener('popstate', handleUrlChange);
|
|
382
|
+
window.removeEventListener('hashchange', handleUrlChange);
|
|
383
|
+
observer.disconnect();
|
|
384
|
+
};
|
|
385
|
+
}, [handleUrlChange]);
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
return {
|
|
389
|
+
isReady,
|
|
390
|
+
history: historyRef.current,
|
|
391
|
+
forwardHistory: forwardHistoryRef.current,
|
|
392
|
+
currentUrl,
|
|
393
|
+
firstUrl: firstUrlRef.current,
|
|
394
|
+
clearHistory,
|
|
395
|
+
goToHistory,
|
|
396
|
+
goBack,
|
|
397
|
+
goForward,
|
|
398
|
+
canGoBack,
|
|
399
|
+
canGoForward
|
|
400
|
+
};
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
export default useHistoryTracker;
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"author": "UIUX Lab",
|
|
3
3
|
"email": "uiuxlab@gmail.com",
|
|
4
4
|
"name": "funda-ui",
|
|
5
|
-
"version": "4.7.
|
|
5
|
+
"version": "4.7.115",
|
|
6
6
|
"description": "React components using pure Bootstrap 5+ which does not contain any external style and script libraries.",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|