Skip to content

Commit a7d0c01

Browse files
ENvironmentSetanakin_karrot
andauthored
feat(plugin-history-sync): Add an option to skip default history setup transition (#671)
Co-authored-by: anakin_karrot <anakin@daangn.com>
1 parent 74d24ce commit a7d0c01

File tree

3 files changed

+203
-99
lines changed

3 files changed

+203
-99
lines changed

.changeset/shiny-ears-draw.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@stackflow/plugin-history-sync": minor
3+
---
4+
5+
Add an option to skip default history setup transition

extensions/plugin-history-sync/src/RouteLike.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,14 @@ export type Route<ComponentType> = {
99
decode?: (
1010
params: Record<string, string>,
1111
) => ComponentType extends ActivityComponentType<infer U> ? U : {};
12-
defaultHistory?: (params: Record<string, string>) => HistoryEntry[];
12+
defaultHistory?: (
13+
params: Record<string, string>,
14+
) => HistoryEntry[] | DefaultHistoryDescriptor;
15+
};
16+
17+
export type DefaultHistoryDescriptor = {
18+
entries: HistoryEntry[];
19+
skipDefaultHistorySetupTransition?: boolean;
1320
};
1421

1522
export type HistoryEntry = {
@@ -28,3 +35,22 @@ export type RouteLike<ComponentType> =
2835
| string[]
2936
| Route<ComponentType>
3037
| Route<ComponentType>[];
38+
39+
export function interpretDefaultHistoryOption(
40+
option:
41+
| ((
42+
params: Record<string, string>,
43+
) => HistoryEntry[] | DefaultHistoryDescriptor)
44+
| undefined,
45+
params: Record<string, string>,
46+
): DefaultHistoryDescriptor {
47+
if (!option) return { entries: [] };
48+
49+
const entriesOrDescriptor = option(params);
50+
51+
if (Array.isArray(entriesOrDescriptor)) {
52+
return { entries: entriesOrDescriptor };
53+
}
54+
55+
return entriesOrDescriptor;
56+
}

extensions/plugin-history-sync/src/historySyncPlugin.tsx

Lines changed: 171 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,14 @@ import type { NavigationProcess } from "./NavigationProcess/NavigationProcess";
2828
import { SerialNavigationProcess } from "./NavigationProcess/SerialNavigationProcess";
2929
import { normalizeActivityRouteMap } from "./normalizeActivityRouteMap";
3030
import { Publisher } from "./Publisher";
31-
import type { RouteLike } from "./RouteLike";
31+
import {
32+
type HistoryEntry,
33+
interpretDefaultHistoryOption,
34+
type RouteLike,
35+
} from "./RouteLike";
3236
import { RoutesProvider } from "./RoutesContext";
3337
import { sortActivityRoutes } from "./sortActivityRoutes";
3438

35-
const SECOND = 1000;
36-
const MINUTE = 60 * SECOND;
37-
3839
type ConfigHistorySync = {
3940
makeTemplate: typeof makeTemplate;
4041
urlPatternOptions?: UrlPatternOptions;
@@ -255,112 +256,184 @@ export function historySyncPlugin<
255256
...searchParams,
256257
...pathParams,
257258
};
258-
const defaultHistory =
259-
targetActivityRoute.defaultHistory?.(params) ?? [];
260-
261-
initialSetupProcess = new SerialNavigationProcess([
262-
...defaultHistory.map(
263-
({ activityName, activityParams, additionalSteps = [] }) =>
264-
() => {
265-
const events: (
266-
| Omit<PushedEvent, "eventDate">
267-
| Omit<StepPushedEvent, "eventDate">
268-
)[] = [
269-
{
270-
name: "Pushed",
271-
id: id(),
272-
activityId: id(),
273-
activityName,
274-
activityParams: {
275-
...activityParams,
276-
},
277-
activityContext: {
278-
path: currentPath,
279-
lazyActivityComponentRenderContext: {
280-
shouldRenderImmediately: true,
281-
},
282-
},
283-
},
284-
...additionalSteps.map(
285-
({
286-
stepParams,
287-
hasZIndex,
288-
}): Omit<StepPushedEvent, "eventDate"> => ({
289-
name: "StepPushed",
290-
id: id(),
291-
stepId: id(),
292-
stepParams,
293-
hasZIndex,
294-
}),
295-
),
296-
];
297-
298-
for (const event of events) {
299-
if (event.name === "Pushed") {
300-
activityActivationMonitors.push(
301-
new DefaultHistoryActivityActivationMonitor(
302-
event.activityId,
303-
initialSetupProcess!,
304-
),
305-
);
306-
}
307-
}
308-
309-
return events;
259+
const defaultHistory = interpretDefaultHistoryOption(
260+
targetActivityRoute.defaultHistory,
261+
params,
262+
);
263+
const historyEntryToEvents = ({
264+
activityName,
265+
activityParams,
266+
additionalSteps = [],
267+
}: HistoryEntry): (
268+
| Omit<PushedEvent, "eventDate">
269+
| Omit<StepPushedEvent, "eventDate">
270+
)[] => [
271+
{
272+
name: "Pushed",
273+
id: id(),
274+
activityId: id(),
275+
activityName,
276+
activityParams: {
277+
...activityParams,
278+
},
279+
activityContext: {
280+
path: currentPath,
281+
lazyActivityComponentRenderContext: {
282+
shouldRenderImmediately: true,
310283
},
311-
),
312-
() => [
313-
{
314-
name: "Pushed",
284+
},
285+
},
286+
...additionalSteps.map(
287+
({
288+
stepParams,
289+
hasZIndex,
290+
}): Omit<StepPushedEvent, "eventDate"> => ({
291+
name: "StepPushed",
315292
id: id(),
316-
activityId: id(),
317-
activityName: targetActivityRoute.activityName,
318-
activityParams:
319-
makeTemplate(
320-
targetActivityRoute,
321-
options.urlPatternOptions,
322-
).parse(currentPath) ??
323-
urlSearchParamsToMap(pathToUrl(currentPath).searchParams),
324-
activityContext: {
325-
path: currentPath,
326-
lazyActivityComponentRenderContext: {
327-
shouldRenderImmediately: true,
328-
},
329-
},
293+
stepId: id(),
294+
stepParams,
295+
hasZIndex,
296+
}),
297+
),
298+
];
299+
const createTargetActivityPushEvent = (): Omit<
300+
PushedEvent,
301+
"eventDate"
302+
> => ({
303+
name: "Pushed",
304+
id: id(),
305+
activityId: id(),
306+
activityName: targetActivityRoute.activityName,
307+
activityParams:
308+
makeTemplate(targetActivityRoute, options.urlPatternOptions).parse(
309+
currentPath,
310+
) ?? urlSearchParamsToMap(pathToUrl(currentPath).searchParams),
311+
activityContext: {
312+
path: currentPath,
313+
lazyActivityComponentRenderContext: {
314+
shouldRenderImmediately: true,
330315
},
331-
],
332-
]);
316+
},
317+
});
318+
319+
if (defaultHistory.skipDefaultHistorySetupTransition) {
320+
initialSetupProcess = new SerialNavigationProcess([
321+
() => [
322+
...defaultHistory.entries.flatMap((historyEntry) =>
323+
historyEntryToEvents(historyEntry).map((event) => {
324+
if (event.name !== "Pushed") return event;
325+
326+
activityActivationMonitors.push(
327+
new DefaultHistoryActivityActivationMonitor(
328+
event.activityId,
329+
initialSetupProcess!,
330+
),
331+
);
332+
333+
return {
334+
...event,
335+
skipEnterActiveState: true,
336+
};
337+
}),
338+
),
339+
{
340+
...createTargetActivityPushEvent(),
341+
skipEnterActiveState: true,
342+
},
343+
],
344+
]);
345+
} else {
346+
initialSetupProcess = new SerialNavigationProcess([
347+
...defaultHistory.entries.map((historyEntry) => () => {
348+
return historyEntryToEvents(historyEntry).map((event) => {
349+
if (event.name !== "Pushed") return event;
350+
351+
activityActivationMonitors.push(
352+
new DefaultHistoryActivityActivationMonitor(
353+
event.activityId,
354+
initialSetupProcess!,
355+
),
356+
);
333357

334-
return initialSetupProcess
358+
return {
359+
...event,
360+
};
361+
});
362+
}),
363+
() => [createTargetActivityPushEvent()],
364+
]);
365+
}
366+
367+
const now = Date.now();
368+
const initialEvents = initialSetupProcess
335369
.captureNavigationOpportunity(null)
336-
.map((event) => ({
370+
.map((event, index, array) => ({
337371
...event,
338-
eventDate: Date.now() - MINUTE,
372+
eventDate: now - (array.length - index),
339373
}));
374+
const firstPushEvent = initialEvents.find(
375+
(event) => event.name === "Pushed",
376+
);
377+
378+
return initialEvents.map((event) => {
379+
if (event.id !== firstPushEvent?.id) return event;
380+
381+
return {
382+
...event,
383+
skipEnterActiveState: true,
384+
};
385+
});
340386
},
341387
onInit({ actions: { getStack, dispatchEvent, push, stepPush } }) {
342388
const stack = getStack();
343-
const rootActivity = stack.activities[0];
344-
345-
const match = activityRoutes.find(
346-
(r) => r.activityName === rootActivity.name,
347-
)!;
348-
const template = makeTemplate(match, options.urlPatternOptions);
349389

350-
const lastStep = last(rootActivity.steps);
390+
if (parseState(history.location.state) === null) {
391+
for (const activity of stack.activities) {
392+
if (
393+
activity.transitionState === "enter-active" ||
394+
activity.transitionState === "enter-done"
395+
) {
396+
const match = activityRoutes.find(
397+
(r) => r.activityName === activity.name,
398+
)!;
399+
const template = makeTemplate(match, options.urlPatternOptions);
400+
401+
if (activity.isRoot) {
402+
replaceState({
403+
history,
404+
pathname: template.fill(activity.params),
405+
state: {
406+
activity: activity,
407+
},
408+
useHash: options.useHash,
409+
});
410+
} else {
411+
pushState({
412+
history,
413+
pathname: template.fill(activity.params),
414+
state: {
415+
activity: activity,
416+
},
417+
useHash: options.useHash,
418+
});
419+
}
351420

352-
requestHistoryTick(() => {
353-
silentFlag = true;
354-
replaceState({
355-
history,
356-
pathname: template.fill(rootActivity.params),
357-
state: {
358-
activity: rootActivity,
359-
step: lastStep,
360-
},
361-
useHash: options.useHash,
362-
});
363-
});
421+
for (const step of activity.steps) {
422+
if (!step.exitedBy && step.enteredBy.name !== "Pushed") {
423+
pushState({
424+
history,
425+
pathname: template.fill(step.params),
426+
state: {
427+
activity: activity,
428+
step: step,
429+
},
430+
useHash: options.useHash,
431+
});
432+
}
433+
}
434+
}
435+
}
436+
}
364437

365438
const onPopState: Listener = (e) => {
366439
if (silentFlag) {

0 commit comments

Comments
 (0)