@ahnseungchan/webviewkit 2.1.0 → 2.2.0

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/index.js CHANGED
@@ -44,6 +44,7 @@ __export(index_exports, {
44
44
  FLUTTER_READY_EVENT: () => FLUTTER_READY_EVENT,
45
45
  FLUTTER_UNIFIED_HANDLER: () => FLUTTER_UNIFIED_HANDLER,
46
46
  HistoryAction: () => HistoryAction,
47
+ KEEP_ALIVE_LIMIT: () => KEEP_ALIVE_LIMIT,
47
48
  RN_BRIDGE_HANDLER: () => RN_BRIDGE_HANDLER,
48
49
  STACK_BRIDGE_METHODS: () => STACK_BRIDGE_METHODS,
49
50
  STACK_TRANSITION_MS: () => STACK_TRANSITION_MS,
@@ -77,6 +78,7 @@ __export(index_exports, {
77
78
  serializeBridgeMessage: () => serializeBridgeMessage,
78
79
  toStackPath: () => toStackPath,
79
80
  useActivity: () => useActivity,
81
+ useOnRevive: () => useOnRevive,
80
82
  useParams: () => useParams,
81
83
  useStackRouter: () => useStackRouter,
82
84
  waitForBridgeHost: () => waitForBridgeHost
@@ -90,6 +92,8 @@ var BACK_TIMEOUT_MS = 1e3;
90
92
  var BRIDGE_TIMEOUT_MS = 5e3;
91
93
  var BRIDGE_HOST_WAIT_MS = 3e3;
92
94
  var BRIDGE_HOST_POLL_MS = 100;
95
+ var KEEP_ALIVE_LIMIT = 5;
96
+ var DEV_MOUNTED_PAGES_WARN = 15;
93
97
 
94
98
  // src/bridge/detect-host.ts
95
99
  var FLUTTER_READY_EVENT = "flutterInAppWebViewPlatformReady";
@@ -864,19 +868,55 @@ var import_styled4 = __toESM(require("@emotion/styled"));
864
868
 
865
869
  // src/stack-router/stack-router-provider.tsx
866
870
  var import_styled3 = __toESM(require("@emotion/styled"));
867
- var import_react9 = require("react");
871
+ var import_react10 = require("react");
868
872
 
869
873
  // src/stack-router/_components/header.tsx
870
874
  var import_styled = __toESM(require("@emotion/styled"));
875
+ var import_react4 = require("react");
876
+
877
+ // src/stack-router/activity-context.tsx
878
+ var import_react3 = require("react");
879
+ var ActivityContext = (0, import_react3.createContext)(null);
880
+ function useActivity() {
881
+ const context = (0, import_react3.useContext)(ActivityContext);
882
+ if (!context) {
883
+ throw new Error(
884
+ "useActivity must be used inside a StackRouterProvider page"
885
+ );
886
+ }
887
+ return context;
888
+ }
889
+ function useParams() {
890
+ return useActivity().params;
891
+ }
892
+ function useOnRevive(callback) {
893
+ const { revision, data } = useActivity();
894
+ const callbackRef = (0, import_react3.useRef)(callback);
895
+ callbackRef.current = callback;
896
+ const lastRevisionRef = (0, import_react3.useRef)(revision);
897
+ (0, import_react3.useEffect)(() => {
898
+ if (revision > lastRevisionRef.current) callbackRef.current(data);
899
+ lastRevisionRef.current = revision;
900
+ }, [revision, data]);
901
+ }
902
+
903
+ // src/stack-router/_components/header.tsx
871
904
  var import_jsx_runtime3 = require("@emotion/react/jsx-runtime");
872
905
  var Header = ({ title }) => {
906
+ var _a;
907
+ const activity = (0, import_react4.useContext)(ActivityContext);
873
908
  const { currentPath: currentPath2, canGoBack } = useStackRouter();
909
+ const path = (_a = activity == null ? void 0 : activity.path) != null ? _a : currentPath2;
910
+ const showBack = activity ? activity.index > 0 : canGoBack;
874
911
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(StyledHeader, { children: [
875
- canGoBack && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(back_router_default, {}),
876
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StyledTitle, { children: title != null ? title : currentPath2 })
912
+ showBack && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(back_router_default, {}),
913
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StyledTitle, { children: title != null ? title : path })
877
914
  ] });
878
915
  };
879
916
  var StyledHeader = import_styled.default.header`
917
+ position: sticky;
918
+ top: 0;
919
+ z-index: 1;
880
920
  width: 100%;
881
921
  height: 64px;
882
922
  flex-shrink: 0;
@@ -889,49 +929,54 @@ var StyledHeader = import_styled.default.header`
889
929
  `;
890
930
  var StyledTitle = import_styled.default.h1`
891
931
  margin: 0;
932
+ min-width: 0;
892
933
  font-size: 1.25rem;
893
934
  font-weight: 600;
894
935
  color: var(--wvk-header-color, #111827);
936
+ white-space: nowrap;
937
+ overflow: hidden;
938
+ text-overflow: ellipsis;
895
939
  `;
896
940
  var header_default = Header;
897
941
 
898
942
  // src/stack-router/_components/stack-page.tsx
899
- var import_react3 = require("@emotion/react");
943
+ var import_react5 = require("@emotion/react");
900
944
  var import_styled2 = __toESM(require("@emotion/styled"));
901
- var import_react4 = require("react");
945
+ var import_react6 = require("react");
902
946
  var import_jsx_runtime4 = require("@emotion/react/jsx-runtime");
903
947
  var IOS_EASING = "cubic-bezier(0.32, 0.72, 0, 1)";
904
948
  var FADE_EASING = "ease";
905
949
  var PARALLAX_OFFSET = "-30%";
906
- var coverIn = import_react3.keyframes`
907
- from { transform: translate3d(100%, 0, 0); }
908
- to { transform: translate3d(0, 0, 0); }
950
+ var EDGE_SHADOW = "var(--wvk-edge-shadow, -6px 0 24px rgba(0, 0, 0, 0.12))";
951
+ var coverIn = import_react5.keyframes`
952
+ from { transform: translate3d(100%, 0, 0); box-shadow: none; }
953
+ to { transform: translate3d(0, 0, 0); box-shadow: ${EDGE_SHADOW}; }
909
954
  `;
910
- var coverOut = import_react3.keyframes`
911
- from { transform: translate3d(0, 0, 0); }
912
- to { transform: translate3d(100%, 0, 0); }
955
+ var coverOut = import_react5.keyframes`
956
+ from { transform: translate3d(0, 0, 0); box-shadow: ${EDGE_SHADOW}; }
957
+ to { transform: translate3d(100%, 0, 0); box-shadow: none; }
913
958
  `;
914
- var parallaxOut = import_react3.keyframes`
959
+ var parallaxOut = import_react5.keyframes`
915
960
  from { transform: translate3d(0, 0, 0); }
916
961
  to { transform: translate3d(${PARALLAX_OFFSET}, 0, 0); }
917
962
  `;
918
- var parallaxIn = import_react3.keyframes`
963
+ var parallaxIn = import_react5.keyframes`
919
964
  from { transform: translate3d(${PARALLAX_OFFSET}, 0, 0); }
920
965
  to { transform: translate3d(0, 0, 0); }
921
966
  `;
922
- var dimIn = import_react3.keyframes`
967
+ var dimIn = import_react5.keyframes`
923
968
  from { opacity: 0; }
924
969
  to { opacity: 1; }
925
970
  `;
926
- var dimOut = import_react3.keyframes`
971
+ var dimOut = import_react5.keyframes`
927
972
  from { opacity: 1; }
928
973
  to { opacity: 0; }
929
974
  `;
930
- var fadeIn = import_react3.keyframes`
975
+ var fadeIn = import_react5.keyframes`
931
976
  from { opacity: 0; }
932
977
  to { opacity: 1; }
933
978
  `;
934
- var fadeOut = import_react3.keyframes`
979
+ var fadeOut = import_react5.keyframes`
935
980
  from { opacity: 1; }
936
981
  to { opacity: 0; }
937
982
  `;
@@ -939,12 +984,11 @@ var layerOf = (direction, pageState) => direction === "push" === (pageState ===
939
984
  function slideStyles(direction, pageState, duration) {
940
985
  const timing = `${duration}ms ${IOS_EASING} forwards`;
941
986
  if (layerOf(direction, pageState) === "upper") {
942
- return import_react3.css`
987
+ return import_react5.css`
943
988
  animation: ${direction === "push" ? coverIn : coverOut} ${timing};
944
- box-shadow: var(--wvk-edge-shadow, -6px 0 24px rgba(0, 0, 0, 0.12));
945
989
  `;
946
990
  }
947
- return import_react3.css`
991
+ return import_react5.css`
948
992
  animation: ${direction === "push" ? parallaxOut : parallaxIn} ${timing};
949
993
  &::after {
950
994
  content: "";
@@ -964,18 +1008,18 @@ var PageWrapper = import_styled2.default.div`
964
1008
  -webkit-overflow-scrolling: touch;
965
1009
  background: var(--wvk-page-bg, #ffffff);
966
1010
  z-index: ${({ zIndex }) => zIndex};
967
- visibility: ${({ pageState }) => pageState === "covered" ? "hidden" : "visible"};
1011
+ visibility: ${({ pageState }) => pageState === "covered" || pageState === "cached" ? "hidden" : "visible"};
968
1012
  ${({ pageState, direction, transitionType, duration }) => {
969
1013
  if (pageState !== "enter" && pageState !== "exit") return "";
970
1014
  if (transitionType === "none" || duration === 0) return "";
971
1015
  if (transitionType === "fade") {
972
- return import_react3.css`
1016
+ return import_react5.css`
973
1017
  will-change: opacity;
974
1018
  animation: ${pageState === "enter" ? fadeIn : fadeOut} ${duration}ms
975
1019
  ${FADE_EASING} forwards;
976
1020
  `;
977
1021
  }
978
- return import_react3.css`
1022
+ return import_react5.css`
979
1023
  will-change: transform;
980
1024
  ${slideStyles(direction, pageState, duration)}
981
1025
  `;
@@ -987,8 +1031,8 @@ function StackPage({
987
1031
  children,
988
1032
  ...wrapperProps
989
1033
  }) {
990
- const ref = (0, import_react4.useRef)(null);
991
- (0, import_react4.useLayoutEffect)(() => {
1034
+ const ref = (0, import_react6.useRef)(null);
1035
+ (0, import_react6.useLayoutEffect)(() => {
992
1036
  var _a;
993
1037
  (_a = ref.current) == null ? void 0 : _a.toggleAttribute("inert", !isTop);
994
1038
  }, [isTop]);
@@ -1009,32 +1053,117 @@ function StackPage({
1009
1053
  );
1010
1054
  }
1011
1055
 
1012
- // src/stack-router/activity-context.tsx
1013
- var import_react5 = require("react");
1014
- var ActivityContext = (0, import_react5.createContext)(null);
1015
- function useActivity() {
1016
- const context = (0, import_react5.useContext)(ActivityContext);
1017
- if (!context) {
1018
- throw new Error(
1019
- "useActivity must be used inside a StackRouterProvider page"
1020
- );
1021
- }
1022
- return context;
1023
- }
1024
- function useParams() {
1025
- return useActivity().params;
1026
- }
1027
-
1028
1056
  // src/stack-router/stack-reducer.ts
1029
1057
  var topOf = (entries) => entries[entries.length - 1];
1030
- function createStackState(entry) {
1031
- return { entries: [entry], leaving: [], transition: null };
1058
+ var bumpRevision = (entry) => {
1059
+ var _a;
1060
+ return {
1061
+ ...entry,
1062
+ revision: ((_a = entry.revision) != null ? _a : 0) + 1
1063
+ };
1064
+ };
1065
+ function createStackState(entry, cacheLimit = 0) {
1066
+ return {
1067
+ entries: [entry],
1068
+ leaving: [],
1069
+ transition: null,
1070
+ cached: [],
1071
+ cacheLimit
1072
+ };
1073
+ }
1074
+ function cacheEntries(state, removed) {
1075
+ const { entries, cached, cacheLimit } = state;
1076
+ const incoming = removed.filter((entry) => entry.keepAlive);
1077
+ if (cacheLimit <= 0 || incoming.length === 0) return cached;
1078
+ const incomingPaths = new Set(incoming.map((entry) => entry.path));
1079
+ const merged = [
1080
+ ...cached.filter((entry) => !incomingPaths.has(entry.path)),
1081
+ ...incoming
1082
+ ];
1083
+ const capacity = cacheLimit - entries.filter((e) => e.keepAlive).length;
1084
+ return capacity > 0 ? merged.slice(-capacity) : [];
1085
+ }
1086
+ function withoutPath(list, path) {
1087
+ return list.filter((entry) => entry.path !== path);
1088
+ }
1089
+ function flushLeaving(state, nextEntries) {
1090
+ const alive = new Set(nextEntries.map((entry) => entry.key));
1091
+ const removed = state.leaving.filter((entry) => !alive.has(entry.key));
1092
+ return cacheEntries({ ...state, entries: nextEntries }, removed);
1093
+ }
1094
+ function revive(state, action) {
1095
+ const { direction, index, animate } = action;
1096
+ const entry = bumpRevision(action.entry);
1097
+ const previousTop = topOf(state.entries);
1098
+ const entries = [...state.entries.slice(0, index), entry];
1099
+ const removed = state.entries.slice(index);
1100
+ const cached = state.cached.filter((item) => item.key !== entry.key);
1101
+ const base = { ...state, entries, cached };
1102
+ if (!animate || direction === "pop" && removed.length === 0) {
1103
+ return {
1104
+ ...base,
1105
+ cached: cacheEntries(base, removed),
1106
+ leaving: [],
1107
+ transition: null
1108
+ };
1109
+ }
1110
+ if (direction === "push") {
1111
+ const stillInStack = !removed.includes(previousTop);
1112
+ return {
1113
+ ...base,
1114
+ cached: cacheEntries(
1115
+ base,
1116
+ removed.filter((item) => item !== previousTop)
1117
+ ),
1118
+ leaving: stillInStack ? [] : [previousTop],
1119
+ transition: {
1120
+ type: "push",
1121
+ enterKey: entry.key,
1122
+ exitKey: previousTop.key
1123
+ }
1124
+ };
1125
+ }
1126
+ return {
1127
+ ...base,
1128
+ leaving: removed,
1129
+ transition: {
1130
+ type: "pop",
1131
+ enterKey: entry.key,
1132
+ exitKeys: removed.map((item) => item.key)
1133
+ }
1134
+ };
1032
1135
  }
1136
+ function findRevivable(state, path, opts = {}) {
1137
+ const matches = (entry) => entry.path === path;
1138
+ const fromStack = opts.includeRemovable ? [...state.entries.slice(1)].reverse().find((entry) => entry.keepAlive && matches(entry)) : void 0;
1139
+ return fromStack != null ? fromStack : [...state.cached].reverse().find((entry) => matches(entry));
1140
+ }
1141
+ var OVERWRITES_LEAVING = /* @__PURE__ */ new Set([
1142
+ "PUSH",
1143
+ "POP_TO",
1144
+ "RESET",
1145
+ "RESET_PUSH",
1146
+ "RESTORE",
1147
+ "REVIVE"
1148
+ ]);
1033
1149
  function stackReducer(state, action) {
1150
+ if (!OVERWRITES_LEAVING.has(action.type) || state.leaving.length === 0) {
1151
+ return reduce(state, action);
1152
+ }
1153
+ const settled = { ...state, leaving: [] };
1154
+ const next = reduce(settled, action);
1155
+ if (next === settled) return state;
1156
+ return reduce(
1157
+ { ...settled, cached: flushLeaving(state, next.entries) },
1158
+ action
1159
+ );
1160
+ }
1161
+ function reduce(state, action) {
1034
1162
  switch (action.type) {
1035
1163
  case "PUSH": {
1036
1164
  const previousTop = topOf(state.entries);
1037
1165
  return {
1166
+ ...state,
1038
1167
  entries: [...state.entries, action.entry],
1039
1168
  leaving: [],
1040
1169
  transition: action.animate ? {
@@ -1044,20 +1173,34 @@ function stackReducer(state, action) {
1044
1173
  } : null
1045
1174
  };
1046
1175
  }
1047
- case "REPLACE":
1176
+ case "REPLACE": {
1177
+ const entries = [...state.entries.slice(0, -1), action.entry];
1178
+ const { path } = action.entry;
1048
1179
  return {
1049
1180
  ...state,
1050
- entries: [...state.entries.slice(0, -1), action.entry]
1181
+ entries,
1182
+ cached: cacheEntries(
1183
+ { ...state, entries, cached: withoutPath(state.cached, path) },
1184
+ withoutPath([topOf(state.entries)], path)
1185
+ )
1051
1186
  };
1187
+ }
1052
1188
  case "POP_TO": {
1053
1189
  const index = Math.max(0, action.index);
1054
1190
  if (index >= state.entries.length - 1) return state;
1055
1191
  const entries = state.entries.slice(0, index + 1);
1056
1192
  const leaving = state.entries.slice(index + 1);
1057
1193
  if (!action.animate) {
1058
- return { entries, leaving: [], transition: null };
1194
+ return {
1195
+ ...state,
1196
+ entries,
1197
+ leaving: [],
1198
+ transition: null,
1199
+ cached: cacheEntries({ ...state, entries }, leaving)
1200
+ };
1059
1201
  }
1060
1202
  return {
1203
+ ...state,
1061
1204
  entries,
1062
1205
  leaving,
1063
1206
  transition: {
@@ -1067,21 +1210,80 @@ function stackReducer(state, action) {
1067
1210
  }
1068
1211
  };
1069
1212
  }
1070
- case "RESET":
1071
- return createStackState(action.entry);
1213
+ case "RESET": {
1214
+ const next = createStackState(action.entry, state.cacheLimit);
1215
+ const { path } = action.entry;
1216
+ return {
1217
+ ...next,
1218
+ cached: cacheEntries(
1219
+ {
1220
+ ...state,
1221
+ entries: next.entries,
1222
+ cached: withoutPath(state.cached, path)
1223
+ },
1224
+ withoutPath(state.entries, path)
1225
+ )
1226
+ };
1227
+ }
1228
+ case "REVIVE":
1229
+ return revive(state, action);
1230
+ case "RESET_PUSH": {
1231
+ const [root] = state.entries;
1232
+ const previousTop = topOf(state.entries);
1233
+ const reusedKey = action.entry.key;
1234
+ const isReused = [...state.entries, ...state.cached].some(
1235
+ (entry) => entry.key === reusedKey
1236
+ );
1237
+ const entries = [
1238
+ root,
1239
+ isReused ? bumpRevision(action.entry) : action.entry
1240
+ ];
1241
+ const sameTop = previousTop.key === reusedKey;
1242
+ const dropped = (action.animate ? state.entries.slice(1, -1) : state.entries.slice(1)).filter((entry) => entry.key !== reusedKey);
1243
+ const cached = state.cached.filter((entry) => entry.key !== reusedKey);
1244
+ const exiting = action.animate && !sameTop;
1245
+ return {
1246
+ ...state,
1247
+ entries,
1248
+ cached: cacheEntries({ ...state, entries, cached }, dropped),
1249
+ leaving: exiting ? [previousTop] : [],
1250
+ transition: exiting ? {
1251
+ type: "push",
1252
+ enterKey: action.entry.key,
1253
+ exitKey: previousTop.key
1254
+ } : null
1255
+ };
1256
+ }
1072
1257
  case "RESTORE":
1073
- return { entries: action.entries, leaving: [], transition: null };
1074
- case "END_TRANSITION":
1258
+ return {
1259
+ ...state,
1260
+ entries: action.entries,
1261
+ leaving: [],
1262
+ transition: null
1263
+ };
1264
+ case "END_TRANSITION": {
1075
1265
  if (!state.transition && state.leaving.length === 0) return state;
1076
- return { ...state, leaving: [], transition: null };
1266
+ return {
1267
+ ...state,
1268
+ leaving: [],
1269
+ transition: null,
1270
+ cached: flushLeaving(state, state.entries)
1271
+ };
1272
+ }
1077
1273
  default:
1078
1274
  return state;
1079
1275
  }
1080
1276
  }
1081
1277
  function getRenderedPages(state) {
1082
- const { entries, leaving, transition } = state;
1278
+ const { entries, leaving, transition, cached } = state;
1083
1279
  const top = topOf(entries);
1084
- return [...entries, ...leaving].map((entry) => {
1280
+ const ordered = (transition == null ? void 0 : transition.type) === "push" ? [...entries.slice(0, -1), ...leaving, top] : [...entries, ...leaving];
1281
+ const cachedPages = cached.map((entry) => ({
1282
+ entry,
1283
+ pageState: "cached",
1284
+ isTop: false
1285
+ }));
1286
+ const stackPages = ordered.map((entry) => {
1085
1287
  const isTop = entry.key === top.key;
1086
1288
  let pageState = isTop ? "idle" : "covered";
1087
1289
  if ((transition == null ? void 0 : transition.type) === "push") {
@@ -1094,20 +1296,21 @@ function getRenderedPages(state) {
1094
1296
  }
1095
1297
  return { entry, pageState, isTop };
1096
1298
  });
1299
+ return [...cachedPages, ...stackPages];
1097
1300
  }
1098
1301
 
1099
1302
  // src/stack-router/use-prefers-reduced-motion.ts
1100
- var import_react6 = require("react");
1303
+ var import_react7 = require("react");
1101
1304
  var QUERY = "(prefers-reduced-motion: reduce)";
1102
1305
  var getMediaQuery = () => typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia(QUERY) : null;
1103
1306
  function usePrefersReducedMotion() {
1104
- const [reduced, setReduced] = (0, import_react6.useState)(
1307
+ const [reduced, setReduced] = (0, import_react7.useState)(
1105
1308
  () => {
1106
1309
  var _a, _b;
1107
1310
  return (_b = (_a = getMediaQuery()) == null ? void 0 : _a.matches) != null ? _b : false;
1108
1311
  }
1109
1312
  );
1110
- (0, import_react6.useEffect)(() => {
1313
+ (0, import_react7.useEffect)(() => {
1111
1314
  var _a;
1112
1315
  const mediaQuery = getMediaQuery();
1113
1316
  if (!mediaQuery) return;
@@ -1122,13 +1325,13 @@ function usePrefersReducedMotion() {
1122
1325
  }
1123
1326
 
1124
1327
  // src/stack-router/use-stack-bridge-sync.ts
1125
- var import_react7 = require("react");
1328
+ var import_react8 = require("react");
1126
1329
  function useStackBridgeSync(bridge, router, onBackAtRoot) {
1127
1330
  const enabled = Boolean(bridge);
1128
1331
  const options = typeof bridge === "object" ? bridge : {};
1129
- const latest = (0, import_react7.useRef)({ router, options, onBackAtRoot });
1332
+ const latest = (0, import_react8.useRef)({ router, options, onBackAtRoot });
1130
1333
  latest.current = { router, options, onBackAtRoot };
1131
- (0, import_react7.useEffect)(() => {
1334
+ (0, import_react8.useEffect)(() => {
1132
1335
  if (!enabled) return;
1133
1336
  return installBridgeHost({
1134
1337
  allowUrl: latest.current.options.allowUrl,
@@ -1153,7 +1356,7 @@ function useStackBridgeSync(bridge, router, onBackAtRoot) {
1153
1356
  }, [enabled]);
1154
1357
  const { depth, currentPath: currentPath2 } = router;
1155
1358
  const emit = enabled && options.emitStackChanged !== false;
1156
- (0, import_react7.useEffect)(() => {
1359
+ (0, import_react8.useEffect)(() => {
1157
1360
  if (!emit || !isNativeBridgeHost(detectBridgeHost())) return;
1158
1361
  createWebViewBridgeClient().notifyStackChanged({ depth, path: currentPath2 }).catch((error) => {
1159
1362
  var _a, _b;
@@ -1163,7 +1366,7 @@ function useStackBridgeSync(bridge, router, onBackAtRoot) {
1163
1366
  }
1164
1367
 
1165
1368
  // src/stack-router/use-stack-navigation.ts
1166
- var import_react8 = require("react");
1369
+ var import_react9 = require("react");
1167
1370
 
1168
1371
  // src/stack-router/match-path.ts
1169
1372
  var splitSegments = (pathname) => pathname.split("/").filter((segment) => segment.length > 0);
@@ -1214,37 +1417,57 @@ var isDev2 = () => {
1214
1417
  var _a;
1215
1418
  return typeof process !== "undefined" && ((_a = process.env) == null ? void 0 : _a.NODE_ENV) !== "production";
1216
1419
  };
1217
- function createEntry(hit, path, data, key = createBridgeId()) {
1420
+ function createEntry(hit, path, cacheLimit, data, key = createBridgeId()) {
1218
1421
  return {
1219
1422
  key,
1220
1423
  path,
1221
1424
  pattern: hit.activity.path,
1222
1425
  params: hit.match.params,
1223
1426
  query: hit.match.query,
1224
- data
1427
+ data,
1428
+ keepAlive: hit.activity.keepAlive === true && cacheLimit > 0
1225
1429
  };
1226
1430
  }
1227
1431
  var currentUrl = () => window.location.pathname + window.location.search;
1228
1432
  function useStackNavigation(options) {
1229
- const optionsRef = (0, import_react8.useRef)(options);
1433
+ const optionsRef = (0, import_react9.useRef)(options);
1230
1434
  optionsRef.current = options;
1231
- const [state, dispatch] = (0, import_react8.useReducer)(stackReducer, void 0, () => {
1435
+ const [state, dispatch] = (0, import_react9.useReducer)(stackReducer, void 0, () => {
1436
+ var _a;
1232
1437
  const { initialActivity } = options;
1438
+ const cacheLimit = (_a = options.keepAliveLimit) != null ? _a : KEEP_ALIVE_LIMIT;
1233
1439
  return createStackState(
1234
1440
  createEntry(
1235
1441
  { activity: initialActivity, match: { params: {}, query: {} } },
1236
- initialActivity.path
1237
- )
1442
+ initialActivity.path,
1443
+ cacheLimit
1444
+ ),
1445
+ cacheLimit
1238
1446
  );
1239
1447
  });
1240
- const stateRef = (0, import_react8.useRef)(state);
1448
+ const stateRef = (0, import_react9.useRef)(state);
1241
1449
  stateRef.current = state;
1242
- const busyRef = (0, import_react8.useRef)(false);
1243
- const resolve = (0, import_react8.useCallback)((path) => {
1450
+ const newEntry = (hit, path, data, key) => createEntry(hit, path, stateRef.current.cacheLimit, data, key);
1451
+ const busyRef = (0, import_react9.useRef)(false);
1452
+ const ignorePopRef = (0, import_react9.useRef)(0);
1453
+ const pendingTravelRef = (0, import_react9.useRef)(0);
1454
+ const isLocked = () => busyRef.current || pendingTravelRef.current > 0;
1455
+ const travel = (delta, ignorePop) => {
1456
+ pendingTravelRef.current += 1;
1457
+ if (ignorePop) ignorePopRef.current += 1;
1458
+ return history.travel(delta).then((popped) => {
1459
+ pendingTravelRef.current -= 1;
1460
+ if (!popped && ignorePop) {
1461
+ ignorePopRef.current = Math.max(0, ignorePopRef.current - 1);
1462
+ }
1463
+ return popped;
1464
+ });
1465
+ };
1466
+ const resolve = (0, import_react9.useCallback)((path) => {
1244
1467
  const { activities, initialActivity } = optionsRef.current;
1245
1468
  return findActivity([...activities, initialActivity], path);
1246
1469
  }, []);
1247
- const resolveOrNotify = (0, import_react8.useCallback)(
1470
+ const resolveOrNotify = (0, import_react9.useCallback)(
1248
1471
  (path) => {
1249
1472
  var _a, _b;
1250
1473
  const hit = resolve(path);
@@ -1259,29 +1482,35 @@ function useStackNavigation(options) {
1259
1482
  [resolve]
1260
1483
  );
1261
1484
  const animate = () => optionsRef.current.duration > 0;
1262
- const push = (0, import_react8.useCallback)(
1485
+ const push = (0, import_react9.useCallback)(
1263
1486
  (path, data) => {
1264
- if (busyRef.current) return false;
1487
+ if (isLocked()) return false;
1265
1488
  const hit = resolveOrNotify(path);
1266
1489
  if (!hit) return false;
1267
- const entry = createEntry(hit, path, data);
1490
+ const { entries } = stateRef.current;
1491
+ const revived = findRevivable(stateRef.current, path);
1492
+ const entry = revived ? { ...revived, data } : newEntry(hit, path, data);
1268
1493
  busyRef.current = true;
1269
- history.push(path, {
1270
- key: entry.key,
1271
- index: stateRef.current.entries.length,
1272
- data
1273
- });
1274
- dispatch({ type: "PUSH", entry, animate: animate() });
1494
+ history.push(path, { key: entry.key, index: entries.length, data });
1495
+ dispatch(
1496
+ revived ? {
1497
+ type: "REVIVE",
1498
+ entry,
1499
+ direction: "push",
1500
+ index: entries.length,
1501
+ animate: animate()
1502
+ } : { type: "PUSH", entry, animate: animate() }
1503
+ );
1275
1504
  return true;
1276
1505
  },
1277
1506
  [resolveOrNotify]
1278
1507
  );
1279
- const replace = (0, import_react8.useCallback)(
1508
+ const replace = (0, import_react9.useCallback)(
1280
1509
  (path, data) => {
1281
- if (busyRef.current) return false;
1510
+ if (isLocked()) return false;
1282
1511
  const hit = resolveOrNotify(path);
1283
1512
  if (!hit) return false;
1284
- const entry = createEntry(hit, path, data);
1513
+ const entry = newEntry(hit, path, data);
1285
1514
  history.replace(path, {
1286
1515
  key: entry.key,
1287
1516
  index: stateRef.current.entries.length - 1,
@@ -1292,21 +1521,46 @@ function useStackNavigation(options) {
1292
1521
  },
1293
1522
  [resolveOrNotify]
1294
1523
  );
1295
- const reset = (0, import_react8.useCallback)(
1524
+ const reset = (0, import_react9.useCallback)(
1296
1525
  (path, data) => {
1297
- if (busyRef.current) return false;
1526
+ if (isLocked()) return false;
1298
1527
  const hit = resolveOrNotify(path);
1299
1528
  if (!hit) return false;
1300
- const entry = createEntry(hit, path, data);
1301
- history.replace(path, { key: entry.key, index: 0, data });
1529
+ const entry = newEntry(hit, path, data);
1530
+ const depth = stateRef.current.entries.length;
1302
1531
  dispatch({ type: "RESET", entry });
1532
+ const commit = () => {
1533
+ history.replace(path, { key: entry.key, index: 0, data });
1534
+ };
1535
+ if (depth === 1) commit();
1536
+ else void travel(-(depth - 1), true).then(commit);
1303
1537
  return true;
1304
1538
  },
1305
1539
  [resolveOrNotify]
1306
1540
  );
1307
- const pop = (0, import_react8.useCallback)((count = 1) => {
1541
+ const resetPush = (0, import_react9.useCallback)(
1542
+ (path, data) => {
1543
+ const { entries } = stateRef.current;
1544
+ if (entries.length === 1) return push(path, data);
1545
+ if (isLocked()) return false;
1546
+ const hit = resolveOrNotify(path);
1547
+ if (!hit) return false;
1548
+ const revivable = findRevivable(stateRef.current, path, {
1549
+ includeRemovable: true
1550
+ });
1551
+ const entry = revivable ? { ...revivable, data } : newEntry(hit, path, data);
1552
+ busyRef.current = true;
1553
+ dispatch({ type: "RESET_PUSH", entry, animate: animate() });
1554
+ void travel(-(entries.length - 1), true).then(() => {
1555
+ history.push(path, { key: entry.key, index: 1, data });
1556
+ });
1557
+ return true;
1558
+ },
1559
+ [push, resolveOrNotify]
1560
+ );
1561
+ const pop = (0, import_react9.useCallback)((count = 1) => {
1308
1562
  var _a, _b;
1309
- if (busyRef.current) return false;
1563
+ if (isLocked()) return false;
1310
1564
  const { entries } = stateRef.current;
1311
1565
  if (entries.length <= 1) {
1312
1566
  (_b = (_a = optionsRef.current).onBackAtRoot) == null ? void 0 : _b.call(_a);
@@ -1316,11 +1570,11 @@ function useStackNavigation(options) {
1316
1570
  const targetIndex = entries.length - 1 - delta;
1317
1571
  busyRef.current = true;
1318
1572
  dispatch({ type: "POP_TO", index: targetIndex, animate: animate() });
1319
- void history.travel(-delta);
1573
+ void travel(-delta, false);
1320
1574
  return true;
1321
1575
  }, []);
1322
- const back = (0, import_react8.useCallback)(() => pop(1), [pop]);
1323
- const popTo = (0, import_react8.useCallback)(
1576
+ const back = (0, import_react9.useCallback)(() => pop(1), [pop]);
1577
+ const popTo = (0, import_react9.useCallback)(
1324
1578
  (path) => {
1325
1579
  const { entries } = stateRef.current;
1326
1580
  for (let i = entries.length - 2; i >= 0; i -= 1) {
@@ -1332,11 +1586,15 @@ function useStackNavigation(options) {
1332
1586
  },
1333
1587
  [pop]
1334
1588
  );
1335
- const endTransition = (0, import_react8.useCallback)(() => {
1589
+ const endTransition = (0, import_react9.useCallback)(() => {
1336
1590
  dispatch({ type: "END_TRANSITION" });
1337
1591
  }, []);
1338
- (0, import_react8.useEffect)(() => {
1592
+ (0, import_react9.useEffect)(() => {
1339
1593
  const onPop = (historyState) => {
1594
+ if (ignorePopRef.current > 0) {
1595
+ ignorePopRef.current -= 1;
1596
+ return;
1597
+ }
1340
1598
  const { entries } = stateRef.current;
1341
1599
  const index = historyState.key ? entries.findIndex((entry2) => entry2.key === historyState.key) : -1;
1342
1600
  if (index !== -1) {
@@ -1346,12 +1604,26 @@ function useStackNavigation(options) {
1346
1604
  }
1347
1605
  return;
1348
1606
  }
1607
+ const cachedEntry = historyState.key ? stateRef.current.cached.find((item) => item.key === historyState.key) : void 0;
1608
+ const targetIndex = historyState.index;
1609
+ if (cachedEntry && typeof targetIndex === "number" && // 루트 자리(0)는 되살리지 않는다. 아래 일반 처리(reset)로 넘긴다
1610
+ targetIndex >= 1 && targetIndex <= entries.length) {
1611
+ busyRef.current = true;
1612
+ dispatch({
1613
+ type: "REVIVE",
1614
+ entry: { ...cachedEntry, data: historyState.data },
1615
+ direction: targetIndex === entries.length ? "push" : "pop",
1616
+ index: targetIndex,
1617
+ animate: animate()
1618
+ });
1619
+ return;
1620
+ }
1349
1621
  const hit = resolveOrNotify(historyState.path);
1350
1622
  if (!hit) {
1351
1623
  busyRef.current = false;
1352
1624
  return;
1353
1625
  }
1354
- const entry = createEntry(
1626
+ const entry = newEntry(
1355
1627
  hit,
1356
1628
  historyState.path,
1357
1629
  historyState.data,
@@ -1369,8 +1641,8 @@ function useStackNavigation(options) {
1369
1641
  removeListener();
1370
1642
  };
1371
1643
  }, [resolveOrNotify]);
1372
- const restoredRef = (0, import_react8.useRef)(false);
1373
- (0, import_react8.useEffect)(() => {
1644
+ const restoredRef = (0, import_react9.useRef)(false);
1645
+ (0, import_react9.useEffect)(() => {
1374
1646
  if (restoredRef.current) return;
1375
1647
  restoredRef.current = true;
1376
1648
  const root = stateRef.current.entries[0];
@@ -1381,7 +1653,7 @@ function useStackNavigation(options) {
1381
1653
  return;
1382
1654
  }
1383
1655
  const savedData = history.getCurrentState().data;
1384
- const top = createEntry(hit, url, savedData);
1656
+ const top = newEntry(hit, url, savedData);
1385
1657
  if (optionsRef.current.restoreStack === "top-only") {
1386
1658
  history.replace(url, { key: top.key, index: 0, data: savedData });
1387
1659
  dispatch({ type: "RESTORE", entries: [top] });
@@ -1391,7 +1663,7 @@ function useStackNavigation(options) {
1391
1663
  history.push(url, { key: top.key, index: 1, data: savedData });
1392
1664
  dispatch({ type: "RESTORE", entries: [root, top] });
1393
1665
  }, [resolve]);
1394
- (0, import_react8.useEffect)(() => {
1666
+ (0, import_react9.useEffect)(() => {
1395
1667
  if (!state.transition) {
1396
1668
  busyRef.current = false;
1397
1669
  return;
@@ -1402,14 +1674,24 @@ function useStackNavigation(options) {
1402
1674
  );
1403
1675
  return () => clearTimeout(timer);
1404
1676
  }, [state, endTransition]);
1405
- return { state, push, replace, reset, back, pop, popTo, endTransition };
1677
+ return {
1678
+ state,
1679
+ push,
1680
+ replace,
1681
+ reset,
1682
+ resetPush,
1683
+ back,
1684
+ pop,
1685
+ popTo,
1686
+ endTransition
1687
+ };
1406
1688
  }
1407
1689
 
1408
1690
  // src/stack-router/stack-router-provider.tsx
1409
1691
  var import_jsx_runtime5 = require("@emotion/react/jsx-runtime");
1410
- var StackRouterContext = (0, import_react9.createContext)(null);
1692
+ var StackRouterContext = (0, import_react10.createContext)(null);
1411
1693
  var useStackRouter = () => {
1412
- const context = (0, import_react9.useContext)(StackRouterContext);
1694
+ const context = (0, import_react10.useContext)(StackRouterContext);
1413
1695
  if (!context) {
1414
1696
  throw new Error("useStackRouter must be used within a StackRouterProvider");
1415
1697
  }
@@ -1419,6 +1701,8 @@ var isDevEnv = () => {
1419
1701
  var _a;
1420
1702
  return typeof process !== "undefined" && ((_a = process.env) == null ? void 0 : _a.NODE_ENV) === "development";
1421
1703
  };
1704
+ var MIN_MOUNTED_PAGES = 1;
1705
+ var CACHED_ACTIVITY_INDEX = -1;
1422
1706
  var StackRouterProvider = ({
1423
1707
  initialActivity,
1424
1708
  Activities = [],
@@ -1428,6 +1712,8 @@ var StackRouterProvider = ({
1428
1712
  transition = "slide",
1429
1713
  duration = STACK_TRANSITION_MS,
1430
1714
  restoreStack = "with-root",
1715
+ keepAliveLimit = KEEP_ALIVE_LIMIT,
1716
+ maxMountedPages,
1431
1717
  safeArea = false,
1432
1718
  onBackAtRoot,
1433
1719
  onNotFound,
@@ -1441,6 +1727,7 @@ var StackRouterProvider = ({
1441
1727
  activities: Activities,
1442
1728
  duration: effectiveDuration,
1443
1729
  restoreStack,
1730
+ keepAliveLimit,
1444
1731
  onBackAtRoot,
1445
1732
  onNotFound
1446
1733
  });
@@ -1450,7 +1737,7 @@ var StackRouterProvider = ({
1450
1737
  return (_b2 = (_a2 = Activities.find((activity) => activity.path === pattern)) == null ? void 0 : _a2.element) != null ? _b2 : initialActivity.path === pattern ? initialActivity.element : null;
1451
1738
  };
1452
1739
  const top = state.entries[state.entries.length - 1];
1453
- const value = (0, import_react9.useMemo)(
1740
+ const value = (0, import_react10.useMemo)(
1454
1741
  () => ({
1455
1742
  currentPath: top.path,
1456
1743
  currentState: {
@@ -1472,6 +1759,7 @@ var StackRouterProvider = ({
1472
1759
  push: navigation.push,
1473
1760
  replace: navigation.replace,
1474
1761
  reset: navigation.reset,
1762
+ resetPush: navigation.resetPush,
1475
1763
  back: navigation.back,
1476
1764
  pop: navigation.pop,
1477
1765
  popTo: navigation.popTo,
@@ -1482,11 +1770,60 @@ var StackRouterProvider = ({
1482
1770
  useStackBridgeSync(bridge, value, onBackAtRoot);
1483
1771
  const direction = (_b = (_a = state.transition) == null ? void 0 : _a.type) != null ? _b : "push";
1484
1772
  const pages = getRenderedPages(state);
1773
+ const stackIndexByKey = new Map(
1774
+ state.entries.map((entry, index) => [entry.key, index])
1775
+ );
1776
+ const activityIndexOf = (entry) => {
1777
+ const stackIndex = stackIndexByKey.get(entry.key);
1778
+ if (stackIndex !== void 0) return stackIndex;
1779
+ const leavingIndex = state.leaving.findIndex(
1780
+ (item) => item.key === entry.key
1781
+ );
1782
+ return leavingIndex === -1 ? CACHED_ACTIVITY_INDEX : state.entries.length + leavingIndex;
1783
+ };
1784
+ const mountLimit = maxMountedPages === void 0 || !Number.isFinite(maxMountedPages) ? Infinity : Math.max(MIN_MOUNTED_PAGES, maxMountedPages);
1785
+ const mountedKeysRef = (0, import_react10.useRef)(/* @__PURE__ */ new Set());
1786
+ const wasMounted = mountedKeysRef.current;
1787
+ const shouldMount = (page) => {
1788
+ const { entry, pageState } = page;
1789
+ if (pageState === "cached" || entry.keepAlive) return true;
1790
+ const stackIndex = stackIndexByKey.get(entry.key);
1791
+ if (stackIndex === void 0) return wasMounted.has(entry.key);
1792
+ if (pageState === "enter" || pageState === "exit") return true;
1793
+ return state.entries.length - stackIndex <= mountLimit;
1794
+ };
1795
+ const mountedKeys = new Set(
1796
+ pages.filter(shouldMount).map((page) => page.entry.key)
1797
+ );
1798
+ mountedKeysRef.current = mountedKeys;
1799
+ const mountedCount = mountedKeys.size;
1800
+ const domOrderRef = (0, import_react10.useRef)([]);
1801
+ const currentKeys = new Set(pages.map((page) => page.entry.key));
1802
+ const knownKeys = new Set(domOrderRef.current);
1803
+ domOrderRef.current = [
1804
+ ...domOrderRef.current.filter((key) => currentKeys.has(key)),
1805
+ ...pages.map((page) => page.entry.key).filter((key) => !knownKeys.has(key))
1806
+ ];
1807
+ const domOrder = domOrderRef.current;
1808
+ const domPages = pages.map((page, index) => ({ page, index })).sort(
1809
+ (a, b) => domOrder.indexOf(a.page.entry.key) - domOrder.indexOf(b.page.entry.key)
1810
+ );
1811
+ const hasWarnedRef = (0, import_react10.useRef)(false);
1812
+ (0, import_react10.useEffect)(() => {
1813
+ if (hasWarnedRef.current || !isDevEnv() || mountedCount <= DEV_MOUNTED_PAGES_WARN) {
1814
+ return;
1815
+ }
1816
+ hasWarnedRef.current = true;
1817
+ console.warn(
1818
+ `[webviewkit] \uB9C8\uC6B4\uD2B8\uB41C \uD398\uC774\uC9C0\uAC00 ${mountedCount}\uAC1C\uC785\uB2C8\uB2E4. \uBA54\uBAA8\uB9AC \uC0AC\uC6A9\uC774 \uCEE4\uC9C8 \uC218 \uC788\uC73C\uB2C8 maxMountedPages \uB85C \uC0C1\uD55C\uC744 \uB450\uC138\uC694.`
1819
+ );
1820
+ }, [mountedCount]);
1485
1821
  const showDevTools = enableDevTools != null ? enableDevTools : isDevEnv();
1486
1822
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(StackRouterContext.Provider, { value, children: [
1487
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(LayoutContainer, { safeArea, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Layout, { maxWidth, "data-testid": "layout", children: [
1488
- isShowHeader && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(header_default, {}),
1489
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PageContainer, { children: pages.map(({ entry, pageState, isTop }, index) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1823
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(LayoutContainer, { safeArea, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Layout, { maxWidth, "data-testid": "layout", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PageContainer, { children: domPages.map(({ page, index }) => {
1824
+ var _a2;
1825
+ const { entry, pageState, isTop } = page;
1826
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1490
1827
  ActivityContext.Provider,
1491
1828
  {
1492
1829
  value: {
@@ -1496,10 +1833,12 @@ var StackRouterProvider = ({
1496
1833
  params: entry.params,
1497
1834
  query: entry.query,
1498
1835
  data: entry.data,
1499
- index,
1500
- isTop
1836
+ index: activityIndexOf(entry),
1837
+ isTop,
1838
+ isCached: pageState === "cached",
1839
+ revision: (_a2 = entry.revision) != null ? _a2 : 0
1501
1840
  },
1502
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1841
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1503
1842
  StackPage,
1504
1843
  {
1505
1844
  pageState,
@@ -1509,13 +1848,16 @@ var StackRouterProvider = ({
1509
1848
  zIndex: index,
1510
1849
  isTop,
1511
1850
  onAnimationEnd: pageState === "enter" ? endTransition : void 0,
1512
- children: elementFor(entry.pattern)
1851
+ children: [
1852
+ mountedKeys.has(entry.key) && isShowHeader && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(header_default, {}),
1853
+ mountedKeys.has(entry.key) ? elementFor(entry.pattern) : null
1854
+ ]
1513
1855
  }
1514
1856
  )
1515
1857
  },
1516
1858
  entry.key
1517
- )) })
1518
- ] }) }),
1859
+ );
1860
+ }) }) }) }),
1519
1861
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1520
1862
  StackDevRoadmap,
1521
1863
  {
@@ -1620,6 +1962,7 @@ var BackIcon = () => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1620
1962
  FLUTTER_READY_EVENT,
1621
1963
  FLUTTER_UNIFIED_HANDLER,
1622
1964
  HistoryAction,
1965
+ KEEP_ALIVE_LIMIT,
1623
1966
  RN_BRIDGE_HANDLER,
1624
1967
  STACK_BRIDGE_METHODS,
1625
1968
  STACK_TRANSITION_MS,
@@ -1653,6 +1996,7 @@ var BackIcon = () => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1653
1996
  serializeBridgeMessage,
1654
1997
  toStackPath,
1655
1998
  useActivity,
1999
+ useOnRevive,
1656
2000
  useParams,
1657
2001
  useStackRouter,
1658
2002
  waitForBridgeHost