@ahnseungchan/webviewkit 1.0.0 → 1.0.2
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/dist/packages/core/src/__tests__/back-router.test.d.ts +0 -0
- package/dist/packages/core/src/__tests__/back-router.test.js +62 -0
- package/dist/packages/core/src/__tests__/history.test.d.ts +1 -0
- package/dist/packages/core/src/__tests__/history.test.js +114 -0
- package/dist/packages/core/src/__tests__/stack-router.test.d.ts +0 -0
- package/dist/packages/core/src/__tests__/stack-router.test.js +185 -0
- package/dist/packages/core/src/history.d.ts +10 -0
- package/dist/packages/core/src/history.js +82 -0
- package/dist/packages/core/src/index.d.ts +5 -0
- package/dist/packages/core/src/index.js +6 -0
- package/dist/packages/core/src/stack-router/__tests__/stack-router.test.d.ts +0 -0
- package/dist/packages/core/src/stack-router/__tests__/stack-router.test.js +153 -0
- package/dist/packages/core/src/stack-router/_components/back-router.d.ts +2 -0
- package/dist/packages/core/src/stack-router/_components/back-router.js +34 -0
- package/dist/packages/core/src/stack-router/_components/header.d.ts +2 -0
- package/dist/packages/core/src/stack-router/_components/header.js +36 -0
- package/dist/packages/core/src/stack-router/stack-router-provider.d.ts +25 -0
- package/dist/packages/core/src/stack-router/stack-router-provider.js +211 -0
- package/dist/packages/core/src/type.d.ts +34 -0
- package/dist/packages/core/src/type.js +11 -0
- package/package.json +4 -4
|
File without changes
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// import React from "react";
|
|
3
|
+
// import { render, screen, fireEvent } from "@testing-library/react";
|
|
4
|
+
// import BackRouter from "../stack-router/_components/back-router";
|
|
5
|
+
// import userEvent from "@testing-library/user-event";
|
|
6
|
+
// import { useStackRouter } from "../stack-router/stack-router-provider";
|
|
7
|
+
// jest.mock("../stack-router/stack-router-provider", () => ({
|
|
8
|
+
// useStackRouter: jest.fn(),
|
|
9
|
+
// }));
|
|
10
|
+
// describe("BackRouter", () => {
|
|
11
|
+
// const mockBack = jest.fn();
|
|
12
|
+
// const user = userEvent.setup();
|
|
13
|
+
// beforeEach(() => {
|
|
14
|
+
// // Reset all mocks before each test
|
|
15
|
+
// jest.clearAllMocks();
|
|
16
|
+
// // Setup default mock implementation
|
|
17
|
+
// (useStackRouter as jest.Mock).mockReturnValue({
|
|
18
|
+
// back: mockBack,
|
|
19
|
+
// });
|
|
20
|
+
// });
|
|
21
|
+
// it("renders back button with icon", () => {
|
|
22
|
+
// render(<BackRouter />);
|
|
23
|
+
// // Check if button exists
|
|
24
|
+
// const button = screen.getByRole("button");
|
|
25
|
+
// expect(button).toBeInTheDocument();
|
|
26
|
+
// // Check if SVG icon exists
|
|
27
|
+
// const svg = button.querySelector("svg");
|
|
28
|
+
// expect(svg).toBeInTheDocument();
|
|
29
|
+
// });
|
|
30
|
+
// it("calls back function when clicked", () => {
|
|
31
|
+
// render(<BackRouter />);
|
|
32
|
+
// const button = screen.getByRole("button");
|
|
33
|
+
// fireEvent.click(button);
|
|
34
|
+
// expect(mockBack).toHaveBeenCalledTimes(1);
|
|
35
|
+
// });
|
|
36
|
+
// it("applies correct styles", () => {
|
|
37
|
+
// render(<BackRouter />);
|
|
38
|
+
// const button = screen.getByRole("button");
|
|
39
|
+
// // Check initial styles
|
|
40
|
+
// expect(button).toHaveStyle({
|
|
41
|
+
// width: "40px",
|
|
42
|
+
// height: "40px",
|
|
43
|
+
// padding: "8px",
|
|
44
|
+
// borderRadius: "50%",
|
|
45
|
+
// });
|
|
46
|
+
// // Check if SVG has correct attributes
|
|
47
|
+
// const svg = button.querySelector("svg");
|
|
48
|
+
// expect(svg).toHaveAttribute("width", "32");
|
|
49
|
+
// expect(svg).toHaveAttribute("height", "32");
|
|
50
|
+
// expect(svg).toHaveAttribute("viewBox", "0 0 24 24");
|
|
51
|
+
// });
|
|
52
|
+
// it("handles hover and active states", async () => {
|
|
53
|
+
// render(<BackRouter />);
|
|
54
|
+
// const button = screen.getByRole("button");
|
|
55
|
+
// // expect hover
|
|
56
|
+
// await user.hover(button);
|
|
57
|
+
// expect(button).toHaveStyle({ color: "#666666" });
|
|
58
|
+
// // expect active
|
|
59
|
+
// await user.pointer({ keys: "[MouseLeft]", target: button });
|
|
60
|
+
// expect(button).toHaveStyle({ color: "#999999" });
|
|
61
|
+
// });
|
|
62
|
+
// });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { history } from "../history";
|
|
2
|
+
// mocking window.history
|
|
3
|
+
const mockHistory = {
|
|
4
|
+
state: null,
|
|
5
|
+
pushState: jest.fn(),
|
|
6
|
+
replaceState: jest.fn(),
|
|
7
|
+
back: jest.fn(),
|
|
8
|
+
forward: jest.fn(),
|
|
9
|
+
go: jest.fn(),
|
|
10
|
+
};
|
|
11
|
+
// mocking window.location
|
|
12
|
+
const mockLocation = {
|
|
13
|
+
pathname: "/",
|
|
14
|
+
};
|
|
15
|
+
// mocking global window
|
|
16
|
+
Object.defineProperty(window, "history", {
|
|
17
|
+
value: mockHistory,
|
|
18
|
+
writable: true,
|
|
19
|
+
});
|
|
20
|
+
Object.defineProperty(window, "location", {
|
|
21
|
+
value: mockLocation,
|
|
22
|
+
writable: true,
|
|
23
|
+
});
|
|
24
|
+
describe("history", () => {
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
jest.clearAllMocks();
|
|
27
|
+
mockHistory.state = null;
|
|
28
|
+
mockLocation.pathname = "/";
|
|
29
|
+
});
|
|
30
|
+
describe("push", () => {
|
|
31
|
+
it("should push new history entry", () => {
|
|
32
|
+
const path = "/test";
|
|
33
|
+
const data = { title: "Test" };
|
|
34
|
+
history.push(path, data);
|
|
35
|
+
expect(mockHistory.pushState).toHaveBeenCalledWith({ path, data, action: "PUSH" }, "", path);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
describe("replace", () => {
|
|
39
|
+
it("should replace current history entry", () => {
|
|
40
|
+
const path = "/test";
|
|
41
|
+
const data = { title: "Test" };
|
|
42
|
+
history.replace(path, data);
|
|
43
|
+
expect(mockHistory.replaceState).toHaveBeenCalledWith({ path, data, action: "REPLACE" }, "", path);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
describe("navigation methods", () => {
|
|
47
|
+
it("should call history.back()", () => {
|
|
48
|
+
history.back();
|
|
49
|
+
expect(mockHistory.back).toHaveBeenCalled();
|
|
50
|
+
});
|
|
51
|
+
it("should call history.forward()", () => {
|
|
52
|
+
history.forward();
|
|
53
|
+
expect(mockHistory.forward).toHaveBeenCalled();
|
|
54
|
+
});
|
|
55
|
+
it("should call history.go()", () => {
|
|
56
|
+
const delta = 2;
|
|
57
|
+
history.go(delta);
|
|
58
|
+
expect(mockHistory.go).toHaveBeenCalledWith(delta);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
describe("getCurrentState", () => {
|
|
62
|
+
it("should return current state", () => {
|
|
63
|
+
const mockState = {
|
|
64
|
+
path: "/test",
|
|
65
|
+
data: { title: "Test" },
|
|
66
|
+
};
|
|
67
|
+
mockHistory.state = mockState;
|
|
68
|
+
const state = history.getCurrentState();
|
|
69
|
+
expect(state).toEqual(mockState);
|
|
70
|
+
});
|
|
71
|
+
it("should return default state when history.state is null", () => {
|
|
72
|
+
mockHistory.state = null;
|
|
73
|
+
mockLocation.pathname = "/default";
|
|
74
|
+
const state = history.getCurrentState();
|
|
75
|
+
expect(state).toEqual({ path: "/default" });
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
describe("addListener", () => {
|
|
79
|
+
it("should add and remove listener", () => {
|
|
80
|
+
const listener = jest.fn();
|
|
81
|
+
const removeListener = history.addListener(listener);
|
|
82
|
+
const testState = {
|
|
83
|
+
path: "/test",
|
|
84
|
+
};
|
|
85
|
+
const popStateEvent = new PopStateEvent("popstate", {
|
|
86
|
+
state: testState,
|
|
87
|
+
});
|
|
88
|
+
window.dispatchEvent(popStateEvent);
|
|
89
|
+
expect(listener).toHaveBeenCalledWith(testState);
|
|
90
|
+
// Delete listener
|
|
91
|
+
removeListener();
|
|
92
|
+
// change state and dispatch event
|
|
93
|
+
const newState = { path: "/new" };
|
|
94
|
+
const newPopStateEvent = new PopStateEvent("popstate", {
|
|
95
|
+
state: newState,
|
|
96
|
+
});
|
|
97
|
+
window.dispatchEvent(newPopStateEvent);
|
|
98
|
+
// listener should not be called anymore
|
|
99
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
100
|
+
});
|
|
101
|
+
it("should not add duplicate listener", () => {
|
|
102
|
+
const listener = jest.fn();
|
|
103
|
+
// add listener twice
|
|
104
|
+
history.addListener(listener);
|
|
105
|
+
history.addListener(listener);
|
|
106
|
+
// change state and dispatch event
|
|
107
|
+
const testState = { path: "/test" };
|
|
108
|
+
const popStateEvent = new PopStateEvent("popstate", { state: testState });
|
|
109
|
+
window.dispatchEvent(popStateEvent);
|
|
110
|
+
// listener should be called only once
|
|
111
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
});
|
|
File without changes
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// import React from "react";
|
|
3
|
+
// import {
|
|
4
|
+
// render,
|
|
5
|
+
// screen,
|
|
6
|
+
// fireEvent,
|
|
7
|
+
// act,
|
|
8
|
+
// waitFor,
|
|
9
|
+
// } from "@testing-library/react";
|
|
10
|
+
// import StackRouter, {
|
|
11
|
+
// useStackRouter,
|
|
12
|
+
// } from "../stack-router/stack-router-provider";
|
|
13
|
+
// import { history } from "../history";
|
|
14
|
+
// // Test component that uses useStackRouter
|
|
15
|
+
// const MockPushComponent = ({ path }: { path: string }) => {
|
|
16
|
+
// const { push } = useStackRouter();
|
|
17
|
+
// return (
|
|
18
|
+
// <button
|
|
19
|
+
// onClick={() => push(path)}
|
|
20
|
+
// data-testid={`push-to-${path.replace("/", "")}`}
|
|
21
|
+
// >
|
|
22
|
+
// Navigate to {path}
|
|
23
|
+
// </button>
|
|
24
|
+
// );
|
|
25
|
+
// };
|
|
26
|
+
// const MockBackComponent = ({ path }: { path: string }) => {
|
|
27
|
+
// const { back } = useStackRouter();
|
|
28
|
+
// return (
|
|
29
|
+
// <button onClick={back} data-testid={`back-to-${path.replace("/", "")}`}>
|
|
30
|
+
// Back
|
|
31
|
+
// </button>
|
|
32
|
+
// );
|
|
33
|
+
// };
|
|
34
|
+
// // Mock components for testing
|
|
35
|
+
// const MockPage1 = () => (
|
|
36
|
+
// <div data-testid="page1">
|
|
37
|
+
// <MockPushComponent path="/page2" />
|
|
38
|
+
// Page1
|
|
39
|
+
// </div>
|
|
40
|
+
// );
|
|
41
|
+
// const MockPage2 = () => (
|
|
42
|
+
// <div data-testid="page2">
|
|
43
|
+
// <MockPushComponent path="/page3" />
|
|
44
|
+
// <MockBackComponent path="/page1" />
|
|
45
|
+
// Page2
|
|
46
|
+
// </div>
|
|
47
|
+
// );
|
|
48
|
+
// const MockPage3 = () => (
|
|
49
|
+
// <div data-testid="page3">
|
|
50
|
+
// <MockBackComponent path="/page2" />
|
|
51
|
+
// Page3
|
|
52
|
+
// </div>
|
|
53
|
+
// );
|
|
54
|
+
// const mockActivities = [
|
|
55
|
+
// { path: "/page1", element: <MockPage1 /> },
|
|
56
|
+
// { path: "/page2", element: <MockPage2 /> },
|
|
57
|
+
// { path: "/page3", element: <MockPage3 /> },
|
|
58
|
+
// ];
|
|
59
|
+
// // Helper function to wait for animations and state updates
|
|
60
|
+
// const waitForStateUpdate = async () => {
|
|
61
|
+
// await act(async () => {
|
|
62
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
63
|
+
// });
|
|
64
|
+
// };
|
|
65
|
+
// describe("StackRouter", () => {
|
|
66
|
+
// it("renders initial page correctly", () => {
|
|
67
|
+
// render(
|
|
68
|
+
// <StackRouter
|
|
69
|
+
// initialActivity={{ path: "/page1", element: <MockPage1 /> }}
|
|
70
|
+
// Activities={mockActivities}
|
|
71
|
+
// />
|
|
72
|
+
// );
|
|
73
|
+
// expect(screen.getByTestId("page1")).toBeInTheDocument();
|
|
74
|
+
// });
|
|
75
|
+
// it("navigates to new page when push is called", async () => {
|
|
76
|
+
// render(
|
|
77
|
+
// <StackRouter
|
|
78
|
+
// initialActivity={{ path: "/page1", element: <MockPage1 /> }}
|
|
79
|
+
// Activities={mockActivities}
|
|
80
|
+
// />
|
|
81
|
+
// );
|
|
82
|
+
// // Click the navigation button
|
|
83
|
+
// await act(async () => {
|
|
84
|
+
// fireEvent.click(screen.getByTestId("push-to-page2"));
|
|
85
|
+
// await waitForStateUpdate();
|
|
86
|
+
// });
|
|
87
|
+
// await waitFor(() => {
|
|
88
|
+
// expect(screen.getByText("Page2")).toBeInTheDocument();
|
|
89
|
+
// });
|
|
90
|
+
// });
|
|
91
|
+
// it("goes back when back is called", async () => {
|
|
92
|
+
// render(
|
|
93
|
+
// <StackRouter
|
|
94
|
+
// initialActivity={{ path: "/page1", element: <MockPage1 /> }}
|
|
95
|
+
// Activities={mockActivities}
|
|
96
|
+
// />
|
|
97
|
+
// );
|
|
98
|
+
// // First push to page2
|
|
99
|
+
// await act(async () => {
|
|
100
|
+
// fireEvent.click(screen.getByTestId("push-to-page2"));
|
|
101
|
+
// await waitForStateUpdate();
|
|
102
|
+
// });
|
|
103
|
+
// // Then go back
|
|
104
|
+
// await act(async () => {
|
|
105
|
+
// fireEvent.click(screen.getByTestId("back-to-page1"));
|
|
106
|
+
// await waitForStateUpdate();
|
|
107
|
+
// });
|
|
108
|
+
// await waitFor(() => {
|
|
109
|
+
// expect(screen.getByText("Page1")).toBeInTheDocument();
|
|
110
|
+
// });
|
|
111
|
+
// });
|
|
112
|
+
// it("handles multiple page transitions correctly", async () => {
|
|
113
|
+
// render(
|
|
114
|
+
// <StackRouter
|
|
115
|
+
// initialActivity={{ path: "/page1", element: <MockPage1 /> }}
|
|
116
|
+
// Activities={mockActivities}
|
|
117
|
+
// />
|
|
118
|
+
// );
|
|
119
|
+
// // Navigate through pages
|
|
120
|
+
// await act(async () => {
|
|
121
|
+
// fireEvent.click(screen.getByTestId("push-to-page2"));
|
|
122
|
+
// await waitForStateUpdate();
|
|
123
|
+
// });
|
|
124
|
+
// await waitFor(() => {
|
|
125
|
+
// expect(screen.getByText("Page2")).toBeInTheDocument();
|
|
126
|
+
// });
|
|
127
|
+
// await act(async () => {
|
|
128
|
+
// fireEvent.click(screen.getByTestId("push-to-page3"));
|
|
129
|
+
// await waitForStateUpdate();
|
|
130
|
+
// });
|
|
131
|
+
// await waitFor(() => {
|
|
132
|
+
// expect(screen.getByText("Page3")).toBeInTheDocument();
|
|
133
|
+
// });
|
|
134
|
+
// // Go back twice
|
|
135
|
+
// await act(async () => {
|
|
136
|
+
// fireEvent.click(screen.getByTestId("back-to-page2"));
|
|
137
|
+
// await waitForStateUpdate();
|
|
138
|
+
// });
|
|
139
|
+
// await waitFor(() => {
|
|
140
|
+
// expect(screen.getByText("Page2")).toBeInTheDocument();
|
|
141
|
+
// });
|
|
142
|
+
// await act(async () => {
|
|
143
|
+
// fireEvent.click(screen.getByTestId("back-to-page1"));
|
|
144
|
+
// await waitForStateUpdate();
|
|
145
|
+
// });
|
|
146
|
+
// await waitFor(() => {
|
|
147
|
+
// expect(screen.getByText("Page1")).toBeInTheDocument();
|
|
148
|
+
// });
|
|
149
|
+
// });
|
|
150
|
+
// it("maintains correct page order in stack", async () => {
|
|
151
|
+
// const { rerender } = render(
|
|
152
|
+
// <StackRouter
|
|
153
|
+
// initialActivity={{ path: "/page1", element: <MockPage1 /> }}
|
|
154
|
+
// Activities={mockActivities}
|
|
155
|
+
// />
|
|
156
|
+
// );
|
|
157
|
+
// // Navigate to page2
|
|
158
|
+
// await act(async () => {
|
|
159
|
+
// fireEvent.click(screen.getByTestId("push-to-page2"));
|
|
160
|
+
// await waitForStateUpdate();
|
|
161
|
+
// });
|
|
162
|
+
// await waitFor(() => {
|
|
163
|
+
// expect(screen.getByText("Page2")).toBeInTheDocument();
|
|
164
|
+
// });
|
|
165
|
+
// // Navigate to page3
|
|
166
|
+
// await act(async () => {
|
|
167
|
+
// fireEvent.click(screen.getByTestId("push-to-page3"));
|
|
168
|
+
// await waitForStateUpdate();
|
|
169
|
+
// });
|
|
170
|
+
// await waitFor(() => {
|
|
171
|
+
// expect(screen.getByText("Page3")).toBeInTheDocument();
|
|
172
|
+
// });
|
|
173
|
+
// });
|
|
174
|
+
// it("handles maxWidth prop correctly", () => {
|
|
175
|
+
// render(
|
|
176
|
+
// <StackRouter
|
|
177
|
+
// initialActivity={{ path: "/page1", element: <MockPage1 /> }}
|
|
178
|
+
// Activities={mockActivities}
|
|
179
|
+
// maxWidth="800px"
|
|
180
|
+
// />
|
|
181
|
+
// );
|
|
182
|
+
// const layout = screen.getByTestId("layout");
|
|
183
|
+
// expect(layout).toHaveStyle({ maxWidth: "800px" });
|
|
184
|
+
// });
|
|
185
|
+
// });
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { HistoryListener, HistoryState } from "./type";
|
|
2
|
+
export declare const history: {
|
|
3
|
+
push: (path: string, data?: any) => Promise<void>;
|
|
4
|
+
replace: (path: string, data: any) => Promise<void>;
|
|
5
|
+
back: () => Promise<void>;
|
|
6
|
+
forward: () => Promise<void>;
|
|
7
|
+
go: (delta: number) => Promise<void>;
|
|
8
|
+
getCurrentState: () => HistoryState;
|
|
9
|
+
addListener: (listener: HistoryListener) => () => boolean;
|
|
10
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { HistoryAction } from "./type";
|
|
2
|
+
const listeners = new Set();
|
|
3
|
+
const handlePopState = (event) => {
|
|
4
|
+
const state = event.state;
|
|
5
|
+
notifyListeners(state);
|
|
6
|
+
};
|
|
7
|
+
// notify to listeners
|
|
8
|
+
const notifyListeners = (state) => {
|
|
9
|
+
listeners.forEach((listener) => listener(state));
|
|
10
|
+
};
|
|
11
|
+
// initialize
|
|
12
|
+
window.addEventListener("popstate", handlePopState);
|
|
13
|
+
export const history = {
|
|
14
|
+
// add new history entry
|
|
15
|
+
push: async (path, data) => {
|
|
16
|
+
const state = {
|
|
17
|
+
path,
|
|
18
|
+
data,
|
|
19
|
+
action: HistoryAction.PUSH,
|
|
20
|
+
};
|
|
21
|
+
window.history.pushState(state, "", path);
|
|
22
|
+
// 애니메이션을 위한 지연
|
|
23
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
24
|
+
notifyListeners(state);
|
|
25
|
+
},
|
|
26
|
+
// replace current history entry
|
|
27
|
+
replace: async (path, data) => {
|
|
28
|
+
const state = {
|
|
29
|
+
path,
|
|
30
|
+
data,
|
|
31
|
+
action: HistoryAction.REPLACE,
|
|
32
|
+
};
|
|
33
|
+
window.history.replaceState(state, "", path);
|
|
34
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
35
|
+
notifyListeners(state);
|
|
36
|
+
},
|
|
37
|
+
// back to previous history entry
|
|
38
|
+
back: async () => {
|
|
39
|
+
window.history.back();
|
|
40
|
+
await new Promise((resolve) => {
|
|
41
|
+
const handlePopState = () => {
|
|
42
|
+
window.removeEventListener("popstate", handlePopState);
|
|
43
|
+
resolve();
|
|
44
|
+
};
|
|
45
|
+
window.addEventListener("popstate", handlePopState);
|
|
46
|
+
});
|
|
47
|
+
const state = {
|
|
48
|
+
path: window.location.pathname,
|
|
49
|
+
action: HistoryAction.POP,
|
|
50
|
+
};
|
|
51
|
+
notifyListeners(state);
|
|
52
|
+
},
|
|
53
|
+
// forward to next history entry
|
|
54
|
+
forward: async () => {
|
|
55
|
+
window.history.forward();
|
|
56
|
+
const state = {
|
|
57
|
+
path: window.location.pathname,
|
|
58
|
+
action: HistoryAction.PUSH,
|
|
59
|
+
};
|
|
60
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
61
|
+
notifyListeners(state);
|
|
62
|
+
},
|
|
63
|
+
// go to specific history entry
|
|
64
|
+
go: async (delta) => {
|
|
65
|
+
window.history.go(delta);
|
|
66
|
+
const state = {
|
|
67
|
+
path: window.location.pathname,
|
|
68
|
+
action: HistoryAction.PUSH,
|
|
69
|
+
};
|
|
70
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
71
|
+
notifyListeners(state);
|
|
72
|
+
},
|
|
73
|
+
// get current history entry
|
|
74
|
+
getCurrentState: () => window.history.state || {
|
|
75
|
+
path: window.location.pathname,
|
|
76
|
+
},
|
|
77
|
+
// change listener to history
|
|
78
|
+
addListener: (listener) => {
|
|
79
|
+
listeners.add(listener);
|
|
80
|
+
return () => listeners.delete(listener);
|
|
81
|
+
},
|
|
82
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { StackRouterProviderProps, VisiblePage, DirectionType, HistoryState, HistoryListener, } from "./type";
|
|
2
|
+
export { Direction, HistoryAction } from "./type";
|
|
3
|
+
export { history } from "./history";
|
|
4
|
+
export { default as StackRouterProvider } from "./stack-router/stack-router-provider";
|
|
5
|
+
export { useStackRouter } from "./stack-router/stack-router-provider";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { Direction, HistoryAction } from "./type";
|
|
2
|
+
// Export history API
|
|
3
|
+
export { history } from "./history";
|
|
4
|
+
// Export stack router components
|
|
5
|
+
export { default as StackRouterProvider } from "./stack-router/stack-router-provider";
|
|
6
|
+
export { useStackRouter } from "./stack-router/stack-router-provider";
|
|
File without changes
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// import React from "react";
|
|
3
|
+
// import { render, screen, fireEvent, act } from "@testing-library/react";
|
|
4
|
+
// import StackRouter from "../stack-router-provider";
|
|
5
|
+
// import { history } from "../../history";
|
|
6
|
+
// // Mock components for testing
|
|
7
|
+
// const MockPage1 = () => <div data-testid="page1">Page 1</div>;
|
|
8
|
+
// const MockPage2 = () => <div data-testid="page2">Page 2</div>;
|
|
9
|
+
// const MockPage3 = () => <div data-testid="page3">Page 3</div>;
|
|
10
|
+
// const mockActivities = [
|
|
11
|
+
// { path: "/page1", element: <MockPage1 /> },
|
|
12
|
+
// { path: "/page2", element: <MockPage2 /> },
|
|
13
|
+
// { path: "/page3", element: <MockPage3 /> },
|
|
14
|
+
// ];
|
|
15
|
+
// // Helper function to wait for animations
|
|
16
|
+
// const waitForAnimation = async () => {
|
|
17
|
+
// await act(async () => {
|
|
18
|
+
// await new Promise((resolve) => setTimeout(resolve, 700)); // 애니메이션 시간과 동일하게 설정
|
|
19
|
+
// });
|
|
20
|
+
// };
|
|
21
|
+
// describe("StackRouter", () => {
|
|
22
|
+
// beforeEach(() => {
|
|
23
|
+
// // Reset history before each test
|
|
24
|
+
// history.push("/page1");
|
|
25
|
+
// });
|
|
26
|
+
// it("renders initial page correctly", () => {
|
|
27
|
+
// render(
|
|
28
|
+
// <StackRouter
|
|
29
|
+
// initialActivity={{ path: "/page1", element: <MockPage1 /> }}
|
|
30
|
+
// Activities={mockActivities}
|
|
31
|
+
// />
|
|
32
|
+
// );
|
|
33
|
+
// expect(screen.getByTestId("page1")).toBeInTheDocument();
|
|
34
|
+
// });
|
|
35
|
+
// it("navigates to new page when push is called", async () => {
|
|
36
|
+
// const { rerender } = render(
|
|
37
|
+
// <StackRouter
|
|
38
|
+
// initialActivity={{ path: "/page1", element: <MockPage1 /> }}
|
|
39
|
+
// Activities={mockActivities}
|
|
40
|
+
// />
|
|
41
|
+
// );
|
|
42
|
+
// // Mock history.push
|
|
43
|
+
// history.push("/page2");
|
|
44
|
+
// // Wait for animation
|
|
45
|
+
// await waitForAnimation();
|
|
46
|
+
// rerender(
|
|
47
|
+
// <StackRouter
|
|
48
|
+
// initialActivity={{ path: "/page1", element: <MockPage1 /> }}
|
|
49
|
+
// Activities={mockActivities}
|
|
50
|
+
// />
|
|
51
|
+
// );
|
|
52
|
+
// // 페이지가 렌더링될 때까지 대기
|
|
53
|
+
// await screen.findByTestId("page2");
|
|
54
|
+
// expect(screen.getByTestId("page2")).toBeInTheDocument();
|
|
55
|
+
// });
|
|
56
|
+
// it("goes back when back is called", async () => {
|
|
57
|
+
// const { rerender } = render(
|
|
58
|
+
// <StackRouter
|
|
59
|
+
// initialActivity={{ path: "/page1", element: <MockPage1 /> }}
|
|
60
|
+
// Activities={mockActivities}
|
|
61
|
+
// />
|
|
62
|
+
// );
|
|
63
|
+
// // First push to page2
|
|
64
|
+
// history.push("/page2");
|
|
65
|
+
// await waitForAnimation();
|
|
66
|
+
// // Then go back
|
|
67
|
+
// history.back();
|
|
68
|
+
// await waitForAnimation();
|
|
69
|
+
// rerender(
|
|
70
|
+
// <StackRouter
|
|
71
|
+
// initialActivity={{ path: "/page1", element: <MockPage1 /> }}
|
|
72
|
+
// Activities={mockActivities}
|
|
73
|
+
// />
|
|
74
|
+
// );
|
|
75
|
+
// // 페이지가 렌더링될 때까지 대기
|
|
76
|
+
// await screen.findByTestId("page1");
|
|
77
|
+
// expect(screen.getByTestId("page1")).toBeInTheDocument();
|
|
78
|
+
// });
|
|
79
|
+
// it("handles multiple page transitions correctly", async () => {
|
|
80
|
+
// const { rerender } = render(
|
|
81
|
+
// <StackRouter
|
|
82
|
+
// initialActivity={{ path: "/page1", element: <MockPage1 /> }}
|
|
83
|
+
// Activities={mockActivities}
|
|
84
|
+
// />
|
|
85
|
+
// );
|
|
86
|
+
// // Navigate through pages
|
|
87
|
+
// history.push("/page2");
|
|
88
|
+
// await waitForAnimation();
|
|
89
|
+
// history.push("/page3");
|
|
90
|
+
// await waitForAnimation();
|
|
91
|
+
// rerender(
|
|
92
|
+
// <StackRouter
|
|
93
|
+
// initialActivity={{ path: "/page1", element: <MockPage1 /> }}
|
|
94
|
+
// Activities={mockActivities}
|
|
95
|
+
// />
|
|
96
|
+
// );
|
|
97
|
+
// // 페이지가 렌더링될 때까지 대기
|
|
98
|
+
// await screen.findByTestId("page3");
|
|
99
|
+
// expect(screen.getByTestId("page3")).toBeInTheDocument();
|
|
100
|
+
// // Go back twice
|
|
101
|
+
// history.back();
|
|
102
|
+
// await waitForAnimation();
|
|
103
|
+
// history.back();
|
|
104
|
+
// await waitForAnimation();
|
|
105
|
+
// rerender(
|
|
106
|
+
// <StackRouter
|
|
107
|
+
// initialActivity={{ path: "/page1", element: <MockPage1 /> }}
|
|
108
|
+
// Activities={mockActivities}
|
|
109
|
+
// />
|
|
110
|
+
// );
|
|
111
|
+
// // 페이지가 렌더링될 때까지 대기
|
|
112
|
+
// await screen.findByTestId("page1");
|
|
113
|
+
// expect(screen.getByTestId("page1")).toBeInTheDocument();
|
|
114
|
+
// });
|
|
115
|
+
// it("maintains correct page order in stack", async () => {
|
|
116
|
+
// const { rerender } = render(
|
|
117
|
+
// <StackRouter
|
|
118
|
+
// initialActivity={{ path: "/page1", element: <MockPage1 /> }}
|
|
119
|
+
// Activities={mockActivities}
|
|
120
|
+
// />
|
|
121
|
+
// );
|
|
122
|
+
// // Navigate to page2
|
|
123
|
+
// history.push("/page2");
|
|
124
|
+
// await waitForAnimation();
|
|
125
|
+
// // Navigate to page3
|
|
126
|
+
// history.push("/page3");
|
|
127
|
+
// await waitForAnimation();
|
|
128
|
+
// rerender(
|
|
129
|
+
// <StackRouter
|
|
130
|
+
// initialActivity={{ path: "/page1", element: <MockPage1 /> }}
|
|
131
|
+
// Activities={mockActivities}
|
|
132
|
+
// />
|
|
133
|
+
// );
|
|
134
|
+
// // 모든 페이지가 렌더링될 때까지 대기
|
|
135
|
+
// await screen.findByTestId("page3");
|
|
136
|
+
// // Check if all pages are in the correct order
|
|
137
|
+
// const pages = screen.getAllByTestId(/page[1-3]/);
|
|
138
|
+
// expect(pages[0]).toHaveTextContent("Page 1");
|
|
139
|
+
// expect(pages[1]).toHaveTextContent("Page 2");
|
|
140
|
+
// expect(pages[2]).toHaveTextContent("Page 3");
|
|
141
|
+
// });
|
|
142
|
+
// it("handles maxWidth prop correctly", () => {
|
|
143
|
+
// const { container } = render(
|
|
144
|
+
// <StackRouter
|
|
145
|
+
// initialActivity={{ path: "/page1", element: <MockPage1 /> }}
|
|
146
|
+
// Activities={mockActivities}
|
|
147
|
+
// maxWidth="800px"
|
|
148
|
+
// />
|
|
149
|
+
// );
|
|
150
|
+
// const layout = container.querySelector('div[style*="max-width: 800px"]');
|
|
151
|
+
// expect(layout).toBeInTheDocument();
|
|
152
|
+
// });
|
|
153
|
+
// });
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import styled from "@emotion/styled";
|
|
3
|
+
import { useStackRouter } from "../stack-router-provider";
|
|
4
|
+
const BackRouter = () => {
|
|
5
|
+
const { back } = useStackRouter();
|
|
6
|
+
return (_jsx(BackButton, { onClick: back, children: _jsx(BackIcon, {}) }));
|
|
7
|
+
};
|
|
8
|
+
export default BackRouter;
|
|
9
|
+
/**
|
|
10
|
+
styles
|
|
11
|
+
*/
|
|
12
|
+
const BackButton = styled.button `
|
|
13
|
+
display: flex;
|
|
14
|
+
align-items: center;
|
|
15
|
+
justify-content: center;
|
|
16
|
+
width: 40px;
|
|
17
|
+
height: 40px;
|
|
18
|
+
border: none;
|
|
19
|
+
background: none;
|
|
20
|
+
cursor: pointer;
|
|
21
|
+
padding: 8px;
|
|
22
|
+
border-radius: 50%;
|
|
23
|
+
transition: all 0.2s;
|
|
24
|
+
color: #000000;
|
|
25
|
+
|
|
26
|
+
&:hover {
|
|
27
|
+
color: #666666;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
&:active {
|
|
31
|
+
color: #999999;
|
|
32
|
+
}
|
|
33
|
+
`;
|
|
34
|
+
const BackIcon = () => (_jsx("svg", { width: "32", height: "32", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: _jsx("path", { d: "M12 20L4 12L12 4", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round" }) }));
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect } from "react";
|
|
3
|
+
import styled from "@emotion/styled";
|
|
4
|
+
import BackRouter from "./back-router";
|
|
5
|
+
import { useStackRouter } from "../stack-router-provider";
|
|
6
|
+
const Header = () => {
|
|
7
|
+
const { currentPath, visiblePages } = useStackRouter();
|
|
8
|
+
const [displayPath, setDisplayPath] = useState(currentPath);
|
|
9
|
+
const [isVisible, setIsVisible] = useState(visiblePages.length > 1);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
const timer = setTimeout(() => {
|
|
12
|
+
setDisplayPath(currentPath);
|
|
13
|
+
setIsVisible(visiblePages.length > 1);
|
|
14
|
+
}, 500);
|
|
15
|
+
return () => clearTimeout(timer);
|
|
16
|
+
}, [currentPath, visiblePages]);
|
|
17
|
+
return (_jsxs(StyledHeader, { children: [isVisible && _jsx(BackRouter, {}), _jsx(StyledTitle, { children: displayPath })] }));
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
styles
|
|
21
|
+
*/
|
|
22
|
+
const StyledHeader = styled.header `
|
|
23
|
+
width: 100%;
|
|
24
|
+
height: 64px;
|
|
25
|
+
background-color: white;
|
|
26
|
+
border-bottom: 1px solid #e5e7eb;
|
|
27
|
+
display: flex;
|
|
28
|
+
align-items: center;
|
|
29
|
+
padding: 0 1rem;
|
|
30
|
+
`;
|
|
31
|
+
const StyledTitle = styled.h1 `
|
|
32
|
+
font-size: 1.25rem;
|
|
33
|
+
font-weight: 600;
|
|
34
|
+
color: #111827;
|
|
35
|
+
`;
|
|
36
|
+
export default Header;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { HistoryState, StackRouterProviderProps } from "../type";
|
|
3
|
+
interface StackRouterContextProps {
|
|
4
|
+
currentPath: string;
|
|
5
|
+
currentState: HistoryState;
|
|
6
|
+
Activities: {
|
|
7
|
+
path: string;
|
|
8
|
+
element: React.ReactNode;
|
|
9
|
+
}[];
|
|
10
|
+
initialActivity: {
|
|
11
|
+
path: string;
|
|
12
|
+
element: React.ReactNode;
|
|
13
|
+
};
|
|
14
|
+
push: (path: string, data?: any) => void;
|
|
15
|
+
replace: (path: string, data?: any) => void;
|
|
16
|
+
back: () => void;
|
|
17
|
+
forward: () => void;
|
|
18
|
+
visiblePages: {
|
|
19
|
+
path: string;
|
|
20
|
+
element: React.ReactNode;
|
|
21
|
+
}[];
|
|
22
|
+
}
|
|
23
|
+
export declare const useStackRouter: () => StackRouterContextProps;
|
|
24
|
+
declare const StackRouter: ({ maxWidth, Activities, initialActivity, }: StackRouterProviderProps) => import("react/jsx-runtime").JSX.Element;
|
|
25
|
+
export default StackRouter;
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext, useEffect, useState } from "react";
|
|
3
|
+
import { history } from "../history";
|
|
4
|
+
import styled from "@emotion/styled";
|
|
5
|
+
import { keyframes, css } from "@emotion/react";
|
|
6
|
+
import { HistoryAction, Direction, } from "../type";
|
|
7
|
+
const StackRouterContext = createContext(null);
|
|
8
|
+
export const useStackRouter = () => {
|
|
9
|
+
const context = useContext(StackRouterContext);
|
|
10
|
+
if (!context) {
|
|
11
|
+
throw new Error("useStackRouter must be used within a StackRouterProvider");
|
|
12
|
+
}
|
|
13
|
+
return context;
|
|
14
|
+
};
|
|
15
|
+
const StackRouter = ({ maxWidth = "600px", Activities = [], initialActivity, }) => {
|
|
16
|
+
const [currentPath, setCurrentPath] = useState(history.getCurrentState().path);
|
|
17
|
+
const [currentState, setCurrentState] = useState(history.getCurrentState());
|
|
18
|
+
const [isTransitioning, setIsTransitioning] = useState(false);
|
|
19
|
+
const [direction, setDirection] = useState(Direction.FORWARD);
|
|
20
|
+
const [visiblePages, setVisiblePages] = useState([]);
|
|
21
|
+
// initialize visible pages
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
setVisiblePages([
|
|
24
|
+
{
|
|
25
|
+
path: initialActivity.path,
|
|
26
|
+
element: initialActivity.element,
|
|
27
|
+
isEntering: true,
|
|
28
|
+
},
|
|
29
|
+
]);
|
|
30
|
+
}, [initialActivity]);
|
|
31
|
+
// listen to history changes
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
const removeListener = history.addListener((state) => {
|
|
34
|
+
if (!isTransitioning || direction !== Direction.BACKWARD) {
|
|
35
|
+
setCurrentPath(state?.path);
|
|
36
|
+
setCurrentState(state);
|
|
37
|
+
}
|
|
38
|
+
setIsTransitioning(true);
|
|
39
|
+
setDirection(state?.action === HistoryAction.POP
|
|
40
|
+
? Direction.BACKWARD
|
|
41
|
+
: Direction.FORWARD);
|
|
42
|
+
});
|
|
43
|
+
return () => {
|
|
44
|
+
removeListener();
|
|
45
|
+
};
|
|
46
|
+
}, [isTransitioning, direction]);
|
|
47
|
+
// handle back
|
|
48
|
+
const handleBack = () => {
|
|
49
|
+
setIsTransitioning(true);
|
|
50
|
+
setDirection(Direction.BACKWARD);
|
|
51
|
+
setTimeout(() => {
|
|
52
|
+
setVisiblePages((prev) => prev.slice(0, -1));
|
|
53
|
+
}, 500);
|
|
54
|
+
history.back();
|
|
55
|
+
};
|
|
56
|
+
// update current path
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (visiblePages.length > 0) {
|
|
59
|
+
setCurrentPath(visiblePages[visiblePages.length - 1].path);
|
|
60
|
+
}
|
|
61
|
+
}, [visiblePages]);
|
|
62
|
+
// handle push
|
|
63
|
+
const handlePush = (path, data) => {
|
|
64
|
+
setIsTransitioning(true);
|
|
65
|
+
setDirection(Direction.FORWARD);
|
|
66
|
+
const currentActivity = Activities.find((a) => a.path === path);
|
|
67
|
+
if (currentActivity) {
|
|
68
|
+
setVisiblePages((prev) => {
|
|
69
|
+
const existingIndex = prev.findIndex((a) => a.path === path);
|
|
70
|
+
if (existingIndex !== -1) {
|
|
71
|
+
const newActivities = [...prev];
|
|
72
|
+
const [removed] = newActivities.splice(existingIndex, 1);
|
|
73
|
+
return [...newActivities, { ...removed, isExiting: true }];
|
|
74
|
+
}
|
|
75
|
+
return [
|
|
76
|
+
...prev,
|
|
77
|
+
{ path, element: currentActivity.element, isExiting: true },
|
|
78
|
+
];
|
|
79
|
+
});
|
|
80
|
+
setTimeout(() => {
|
|
81
|
+
setCurrentPath(path);
|
|
82
|
+
}, 500);
|
|
83
|
+
}
|
|
84
|
+
history.push(path, data);
|
|
85
|
+
};
|
|
86
|
+
const value = {
|
|
87
|
+
currentPath,
|
|
88
|
+
currentState,
|
|
89
|
+
initialActivity,
|
|
90
|
+
Activities,
|
|
91
|
+
visiblePages,
|
|
92
|
+
push: handlePush,
|
|
93
|
+
replace: history.replace,
|
|
94
|
+
back: handleBack,
|
|
95
|
+
forward: history.forward,
|
|
96
|
+
};
|
|
97
|
+
return (_jsx(StackRouterContext.Provider, { value: value, children: _jsx(LayoutContainer, { children: _jsx(Layout, { maxWidth: maxWidth, as: "div", "data-testid": "layout", children: _jsx(PageContainer, { children: visiblePages.map((page, index) => (_jsx(PageWrapper, { isTransitioning: isTransitioning, direction: direction, currentPage: index === visiblePages.length - 1, isEntering: page.isEntering, isExiting: page.isExiting, zIndex: index, children: page.element }, `${page.path}-${index}`))) }) }) }) }));
|
|
98
|
+
};
|
|
99
|
+
export default StackRouter;
|
|
100
|
+
/**styles */
|
|
101
|
+
const LayoutContainer = styled.div `
|
|
102
|
+
width: 100%;
|
|
103
|
+
height: 100vh;
|
|
104
|
+
display: flex;
|
|
105
|
+
justify-content: center;
|
|
106
|
+
background: white;
|
|
107
|
+
`;
|
|
108
|
+
const Layout = styled.div `
|
|
109
|
+
width: 100%;
|
|
110
|
+
max-width: ${({ maxWidth }) => maxWidth};
|
|
111
|
+
height: 100%;
|
|
112
|
+
position: relative;
|
|
113
|
+
overflow: hidden;
|
|
114
|
+
`;
|
|
115
|
+
const PageContainer = styled.div `
|
|
116
|
+
position: relative;
|
|
117
|
+
width: 100%;
|
|
118
|
+
height: 100%;
|
|
119
|
+
overflow: hidden;
|
|
120
|
+
background: white;
|
|
121
|
+
`;
|
|
122
|
+
const slideInRight = keyframes `
|
|
123
|
+
from {
|
|
124
|
+
transform: translateX(100%);
|
|
125
|
+
opacity: 0.2;
|
|
126
|
+
}
|
|
127
|
+
to {
|
|
128
|
+
transform: translateX(0);
|
|
129
|
+
opacity: 1;
|
|
130
|
+
}
|
|
131
|
+
`;
|
|
132
|
+
const slideOutLeft = keyframes `
|
|
133
|
+
from {
|
|
134
|
+
transform: translateX(0);
|
|
135
|
+
opacity: 1;
|
|
136
|
+
}
|
|
137
|
+
to {
|
|
138
|
+
transform: translateX(-30%);
|
|
139
|
+
opacity: 0.2;
|
|
140
|
+
}
|
|
141
|
+
`;
|
|
142
|
+
const slideInLeft = keyframes `
|
|
143
|
+
from {
|
|
144
|
+
transform: translateX(-30%);
|
|
145
|
+
opacity: 0.2;
|
|
146
|
+
}
|
|
147
|
+
to {
|
|
148
|
+
transform: translateX(0);
|
|
149
|
+
opacity: 1;
|
|
150
|
+
}
|
|
151
|
+
`;
|
|
152
|
+
const slideOutRight = keyframes `
|
|
153
|
+
from {
|
|
154
|
+
transform: translateX(0);
|
|
155
|
+
opacity: 1;
|
|
156
|
+
}
|
|
157
|
+
to {
|
|
158
|
+
transform: translateX(100%);
|
|
159
|
+
opacity: 0.2;
|
|
160
|
+
}
|
|
161
|
+
`;
|
|
162
|
+
const PageWrapper = styled.div `
|
|
163
|
+
position: absolute;
|
|
164
|
+
top: 0;
|
|
165
|
+
left: 0;
|
|
166
|
+
width: 100%;
|
|
167
|
+
height: 100%;
|
|
168
|
+
background: white;
|
|
169
|
+
cursor: grab;
|
|
170
|
+
user-select: none;
|
|
171
|
+
-webkit-user-select: none;
|
|
172
|
+
-webkit-touch-callout: none;
|
|
173
|
+
-webkit-tap-highlight-color: transparent;
|
|
174
|
+
z-index: ${({ zIndex }) => zIndex};
|
|
175
|
+
will-change: transform, opacity;
|
|
176
|
+
transform: translate3d(0, 0, 0);
|
|
177
|
+
animation: ${({ isTransitioning, direction, currentPage, isEntering, isExiting, }) => {
|
|
178
|
+
if (!isTransitioning)
|
|
179
|
+
return "none";
|
|
180
|
+
if (direction === "forward") {
|
|
181
|
+
if (isExiting) {
|
|
182
|
+
return css `
|
|
183
|
+
${slideInRight} 0.7s ease-in-out forwards
|
|
184
|
+
`;
|
|
185
|
+
}
|
|
186
|
+
if (isEntering) {
|
|
187
|
+
return css `
|
|
188
|
+
${slideOutLeft} 0.7s ease-in-out forwards
|
|
189
|
+
`;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
if (isExiting) {
|
|
194
|
+
return css `
|
|
195
|
+
${slideOutRight} 0.7s ease-in-out forwards
|
|
196
|
+
`;
|
|
197
|
+
}
|
|
198
|
+
if (isEntering) {
|
|
199
|
+
return css `
|
|
200
|
+
${slideInLeft} 0.7s ease-in-out forwards
|
|
201
|
+
`;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return "none";
|
|
205
|
+
}};
|
|
206
|
+
animation-fill-mode: forwards;
|
|
207
|
+
|
|
208
|
+
&:active {
|
|
209
|
+
cursor: grabbing;
|
|
210
|
+
}
|
|
211
|
+
`;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface StackRouterProviderProps {
|
|
2
|
+
maxWidth?: string;
|
|
3
|
+
isShowHeader?: boolean;
|
|
4
|
+
Activities?: {
|
|
5
|
+
path: string;
|
|
6
|
+
element: React.ReactNode;
|
|
7
|
+
}[];
|
|
8
|
+
initialActivity: {
|
|
9
|
+
path: string;
|
|
10
|
+
element: React.ReactNode;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export interface VisiblePage {
|
|
14
|
+
path: string;
|
|
15
|
+
element: React.ReactNode;
|
|
16
|
+
isEntering?: boolean;
|
|
17
|
+
isExiting?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare enum Direction {
|
|
20
|
+
FORWARD = "forward",
|
|
21
|
+
BACKWARD = "backward"
|
|
22
|
+
}
|
|
23
|
+
export type DirectionType = Direction.FORWARD | Direction.BACKWARD;
|
|
24
|
+
export declare enum HistoryAction {
|
|
25
|
+
PUSH = "PUSH",
|
|
26
|
+
POP = "POP",
|
|
27
|
+
REPLACE = "REPLACE"
|
|
28
|
+
}
|
|
29
|
+
export interface HistoryState {
|
|
30
|
+
path: string;
|
|
31
|
+
data?: any;
|
|
32
|
+
action?: HistoryAction.PUSH | HistoryAction.POP | HistoryAction.REPLACE;
|
|
33
|
+
}
|
|
34
|
+
export type HistoryListener = (state: HistoryState) => void;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export var Direction;
|
|
2
|
+
(function (Direction) {
|
|
3
|
+
Direction["FORWARD"] = "forward";
|
|
4
|
+
Direction["BACKWARD"] = "backward";
|
|
5
|
+
})(Direction || (Direction = {}));
|
|
6
|
+
export var HistoryAction;
|
|
7
|
+
(function (HistoryAction) {
|
|
8
|
+
HistoryAction["PUSH"] = "PUSH";
|
|
9
|
+
HistoryAction["POP"] = "POP";
|
|
10
|
+
HistoryAction["REPLACE"] = "REPLACE";
|
|
11
|
+
})(HistoryAction || (HistoryAction = {}));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ahnseungchan/webviewkit",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Core WebView functionality for stack-based navigation",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.esm.js",
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
"files": [
|
|
9
9
|
"dist"
|
|
10
10
|
],
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
11
14
|
"scripts": {
|
|
12
15
|
"build": "tsc",
|
|
13
16
|
"test": "jest src/__tests__",
|
|
@@ -57,8 +60,5 @@
|
|
|
57
60
|
},
|
|
58
61
|
"dependencies": {
|
|
59
62
|
"gsap": "^3.13.0"
|
|
60
|
-
},
|
|
61
|
-
"publishConfig": {
|
|
62
|
-
"access": "public"
|
|
63
63
|
}
|
|
64
64
|
}
|